package com.xlh.service.statistic.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.BetweenFormater;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.poi.excel.cell.CellUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.google.common.collect.*;
import com.google.common.collect.Table;
import com.xlh.common.ResultEnum;
import com.xlh.dao.statistic.*;
import com.xlh.dao.statistic.ext.*;
import com.xlh.dao.conclude.helper.BusiFullMarkMapperHelper;
import com.xlh.dto.PageDTO;
import com.xlh.dto.conclude.BusiFullMarkExceedDTO;
import com.xlh.dto.course.ChapterBasicInfoDTO;
import com.xlh.dto.course.CourseBasicDTO;
import com.xlh.dto.courseTest.UserCourseTestInfoDTO;
import com.xlh.dto.courseTest.experiment.ExperimentStatisticsTaskPassDTO;
import com.xlh.dto.courseTest.experiment.UserExperimentTimeInfoDTO;
import com.xlh.dto.courseTest.practice.PrictceCourseStatisticsDTO;
import com.xlh.dto.problem.UserProblemAndAnswerDTO;
import com.xlh.dto.quiz.UserQuizStatusDTO;
import com.xlh.dto.statistic.*;
import com.xlh.dto.user.ClassCourseDTO;
import com.xlh.dto.user.UserInfoDTO;
import com.xlh.enums.LocalResourcesEnum;
import com.xlh.enums.conclude.LikedSysFunkEnum;
import com.xlh.enums.course.ChapterEnum;
import com.xlh.enums.course.ChapterTypeEnum;
import com.xlh.enums.user.EventCodeEnum;
import com.xlh.enums.user.UserStatusEnum;
import com.xlh.exception.common.GlobalException;
import com.xlh.exception.common.NotFoundException;
import com.xlh.lock.KeyLock;
import com.xlh.param.statistic.UserInfoParam;
import com.xlh.pojo.conclude.BusiFullMark;
import com.xlh.pojo.course.Course;
import com.xlh.pojo.course.CourseTeacher;
import com.xlh.pojo.course.TheoryLearnTimeStatistics;
import com.xlh.pojo.courseTest.ExperimentReport;
import com.xlh.pojo.statistic.*;
import com.xlh.pojo.support.Statistics;
import com.xlh.pojo.system.Operation;
import com.xlh.pojo.user.Class;
import com.xlh.pojo.user.ClassCourse;
import com.xlh.pojo.user.User;
import com.xlh.pojo.user.UserClass;
import com.xlh.service.conclude.ConcludeBusiFullMarkService;
import com.xlh.service.conclude.ConcludeScoreService;
import com.xlh.service.course.ChapterService;
import com.xlh.service.course.CourseService;
import com.xlh.service.courseTest.*;
import com.xlh.service.problem.ProblemService;
import com.xlh.service.statistic.StatisticService;
import com.xlh.service.system.OperationService;
import com.xlh.service.user.ClassService;
import com.xlh.service.user.UserService;
import com.xlh.support.attendance.po.SettingMarkPO;
import com.xlh.support.attendance.service.AttendanceService;
import com.xlh.support.quiz.constant.QuizPerformanceEnum;
import com.xlh.support.quiz.service.QuizStatisticService;
import com.xlh.util.BeanUtil;
import com.xlh.util.CellStyleUtils;
import com.xlh.util.StatisticExcelUtil;
import com.xlh.virtualization.service.ImageAllocationService;
import com.xlh.websocket.GlobalWebsocket;
import com.xlh.websocket.message.StatisticMessage;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.ss.usermodel.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 统计
 * <p>
 * Created by lx on 2020/9/25.
 */
@Service
@Slf4j
public class StatisticServiceimpl implements StatisticService {

    @Autowired
    private StatisticStudentCourseMapper studentCourseMapper;

    @Autowired
    private StudentCourseMapperExt studentCourseMapperExt;

    @Autowired
    private StatisticStudentTaskMapper studentTaskMapper;

    @Autowired
    private StudentTaskMapperExt studentTaskMapperExt;

    @Autowired
    private StatisticStudentExamMapper studentExamMapper;

    @Autowired
    private StudentExamMapperExt studentExamMapperExt;

    @Autowired
    private StatisticClassMapper statisticClassMapper;

    @Autowired
    private StatisticStudentChapterMapper studentChapterMapper;

    @Autowired
    private StudentChapterMapperExt studentChapterMapperExt;

    @Autowired
    private StatisticStudentMapper studentMapper;

    @Autowired
    private StudentMapperExt studentMapperExt;

    @Autowired
    private ClassService classService;

    @Autowired
    private UserService userService;

    @Autowired
    private ChapterService chapterService;

    @Autowired
    private CourseService courseService;

    @Autowired
    private ExperimentReportService experimentReportService;

    @Autowired
    private PracticeService practiceService;

    @Autowired
    private AttendanceService attendanceService;

    @Autowired
    private ProblemService problemService;

    @Autowired
    private PaperAnswerSheetService paperAnswerSheetService;

    @Autowired
    private QuizStatisticService quizStatisticService;

    @Autowired
    private TheoryService theoryService;

    @Autowired
    private ExperimentStatisticsTaskPassService experimentPassService;

    @Autowired
    private OperationService operationService;

    @Autowired
    private ExperimentVideoStatisticsService experimentVideoService;

    @Autowired
    private KeyLock<String> statisticLock;

    @Autowired
    private ThreadPoolTaskExecutor taskExecutor;

    @Autowired
    private QuestionRepositoryService questionRepositoryService;

    @Autowired
    private ImageAllocationService imageAllocationService;

    @Autowired
    private ConcludeBusiFullMarkService concludeBusiFullMarkService;

    @Autowired
    private StatisticStudentCourseMapper statisticStudentCourseMapper;

    @Autowired
    private ConcludeScoreService concludeScoreService;

    @Autowired
    private BusiFullMarkMapperHelper busiFullMarkMapperHelper;



    @Override
    public StatisticDTO queryCourseClassStatistic(Long courseId, Long classId) {

        // 统计班级
        List<StatisticClass> statisticClasses = queryStatisticClass(classId);
        if (CollectionUtils.isEmpty(statisticClasses)) {
            throw new NotFoundException("暂无统计");
        }
        StatisticClass statisticClass = statisticClasses.get(0);

        // 获取主讲教师信息
        Course courseInfo = courseService.getCourseById(courseId);
        User teacherUser = userService.getUser(courseInfo.getUserId());

        // 获取助教信息
        List<CourseTeacher> courseTeachers = courseService.queryCourseTeacher(
                Lists.newArrayList(courseId), null, classId);

        // 时长统计、提交统计、考勤统计、问题交流统计、随堂提问统计
        StatisticStudentCourse statisticSum = studentCourseMapperExt.queryStatisticSum(courseId, classId);
        if (statisticSum == null || statisticSum.getClassId() == null) {
            throw new NotFoundException("暂无统计");
        }

        statisticClass.setPeopleNum(Integer.valueOf(String.valueOf(statisticSum.getUserId())));
        statisticSum.setUserId(null);

        // 实验通过统计
        List<StatisticStudentTask> taskStatistic = queryStatisticStudentTask(courseId, classId);

        // 考试次数
        List<Long> examIds = queryExamList(courseId, classId);

        // 转换数据
        return convert(statisticSum, taskStatistic, examIds, courseTeachers, teacherUser, statisticClass);
    }

    @Override
    public List<StudentChapterDTO> queryPracticeStatistic(Long courseId, Long classId, Integer errorRate) {
        List<StatisticClass> statisticClasses = queryStatisticClass(classId);
        if (CollectionUtils.isEmpty(statisticClasses)) {
            throw new NotFoundException("暂未更新");
        }
        List<ChapterStatisticDTO> chapters = studentChapterMapperExt.queryChapter(courseId, classId);
        return convertErrorRate(chapters, errorRate);
    }

    @Override
    public String getClassUpdateTime(Long classId) {
        List<StatisticClass> statisticClasses = queryStatisticClass(classId);
        if (CollectionUtils.isEmpty(statisticClasses)) {
            throw new NotFoundException("暂未更新");
        }
        return DateUtil.format(statisticClasses.get(0).getUpdateTime(), "yyyy.MM.dd HH:mm");
    }

    @Override
    public void noAutoUpdate(Long userId, Long classId) {

        List<ClassCourseDTO> classCourses = classService.listClassCourse(
                null, false, null, null, Lists.newArrayList(classId));
        if (CollectionUtils.isEmpty(classCourses)) {
            statisticLock.removeLock("update_statistic:class:" + classId);
            throw new GlobalException("该班级下的全部课程已结课，无法统计");
        }

        taskExecutor.execute(() -> {
            List<Long> courseIds = null;
            try {
                courseIds = updateStatistic(classId);
            } catch (Exception e) {
                log.error("noAutoUpdate error", e);
            } finally {
                // 解锁
                statisticLock.removeLock("update_statistic:class:" + classId);
            }
            // websocket推送
            Set<Long> userIds = Sets.newHashSet(userId);
            userIds.addAll(queryTeacherIds(classId, courseIds));
            StatisticMessage message = StatisticMessage.builder().userId(userId).classId(classId).build();
            GlobalWebsocket.sendMessage(Lists.newArrayList(userIds), message);
        });
    }

    @Override
    @Transactional
    public List<Long> updateStatistic(Long classId) {

        // 获取班级基本信息
        Class classInfo = classService.queryClass(classId);

        // 需要先查询这个班级 对应的所有 未结课的课程
        List<ClassCourseDTO> classCourses = classService.queryClassCourse(Lists.newArrayList(classId));
        List<ClassCourseDTO> courses = Lists.newArrayList();
        if (CollectionUtils.isNotEmpty(classCourses)) {
            courses = classCourses.stream()
                    .filter(classCourseDTO -> !classCourseDTO.getEnded()).collect(Collectors.toList());
        }

        // 未删除的课程id集合 和 所有课程的集合
        List<Long> teachingCourseIds = courses.stream()
                .map(ClassCourseDTO::getCourseId).distinct().collect(Collectors.toList());
        List<Long> courseIds = classCourses.stream()
                .map(ClassCourseDTO::getCourseId).distinct().collect(Collectors.toList());

        // 获取统计中这个班级的用户id
        List<StatisticStudentCourse> statisticStudentCourses = queryStatisticStudentCourse(
                null, classId, null);
        List<Long> userIds = Lists.newArrayList();
        if (CollectionUtils.isNotEmpty(statisticStudentCourses)) {
            userIds = statisticStudentCourses.stream()
                    .map(StatisticStudentCourse::getUserId).distinct().collect(Collectors.toList());
        }

        // 获取这个班级下的学生id集合
        List<UserInfoDTO> userInfos = queryUserInfo(classId);
        Map<Long, UserInfoDTO> userMap = BeanUtil.mapByKey("id", userInfos);
        List<Long> dbStudentIds = userInfos.stream().map(UserInfoDTO::getId).collect(Collectors.toList());
        userMap = convertUserMap(userMap, userIds);

        // 删除classId未绑定的课程的统计数据
        deleteStatistic(classId, courseIds);

        // 删除当前班级未结课的课程中未绑定的学生的统计数据
        deleteStatistic(classId, teachingCourseIds, dbStudentIds);

        // 设置班级统计
        insertOrUpdateClassStatistic(classInfo, dbStudentIds.size());

        teachingCourseIds = teachingCourseIds == null ? Lists.newArrayList() : teachingCourseIds;
        Map<Long, UserInfoDTO> finalUserMap = userMap;
        teachingCourseIds.forEach(courseId -> {

            if (CollectionUtils.isEmpty(dbStudentIds)) return;

            // 章节和任务统计进行删除（现有章节、任务 与 统计表 对不上的数据进行删除）
            List<ChapterBasicInfoDTO> dbChapters = chapterService.listChapterInfo(
                    Lists.newArrayList(courseId), null, null);
            Map<Long, ChapterBasicInfoDTO> chapterMap = BeanUtil.mapByKey("id", dbChapters);
            List<ChapterBasicInfoDTO> allChapters = ChapterEnum.CHAPTER.getConvertData(dbChapters);
            List<Long> dbChapterIds = convertChapter(dbChapters);
            List<Long> dbTaskIds = convertTask(dbChapters);
            deleteStudentTask(courseId, classId, dbTaskIds);
            deleteStudentChapter(courseId, classId, dbChapterIds);

            // 考试统计进行删除（现有考试 与 统计表 对不上的数据进行删除）
            List<UserCourseTestInfoDTO> dbExams = paperAnswerSheetService.findTestStatisticsByUserIds(
                    courseId, classId, dbStudentIds);
            List<Long> dbExamIds = null;
            if (CollectionUtils.isNotEmpty(dbExams)) {
                dbExamIds = dbExams.stream().map(UserCourseTestInfoDTO::getId).distinct().collect(Collectors.toList());
            }
            deleteStudentExam(courseId, classId, dbExamIds);

            // 插入或修改章节/任务/考试统计，返回值为统计数据
            List<StatisticStudentChapter> chapterStatistic = insertOrUpdateChapter(
                    courseId, classId, dbStudentIds, finalUserMap, dbChapterIds, chapterMap);
            List<StatisticStudentTask> taskStatistic = insertOrUpdateTask(
                    courseId, classId, dbStudentIds, finalUserMap, dbTaskIds, chapterMap);
            insertOrUpdateExam(courseId, classId, dbStudentIds, finalUserMap, dbExams);

            // 插入或修改课程统计
            insertOrUpdateCourse(chapterStatistic, taskStatistic, courseId, classId, dbStudentIds, finalUserMap,
                    dbChapterIds, dbTaskIds, CollectionUtils.isEmpty(allChapters) ? 0 : allChapters.size());

        });

        // 插入或修改学生
        Set<Long> updateStudentIds = CollectionUtils.isEmpty(userIds) ? Sets.newHashSet() : Sets.newHashSet(userIds);
        if (CollectionUtils.isNotEmpty(dbStudentIds)) {
            updateStudentIds.addAll(Sets.newHashSet(dbStudentIds));
        }
        insertOrUpdateStudent(Lists.newArrayList(updateStudentIds), userMap);

        return courseIds;
    }

    @Override
    public void downloadCourseClass(Long courseId, Long classId) {

        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                .getRequest();
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                .getResponse();

        // 获取班级课程整体统计信息
        StatisticDTO statistic = null;
        try {
            statistic = queryCourseClassStatistic(courseId, classId);
        } catch (NotFoundException e) {
            response.addHeader("code-message", String.valueOf(ResultEnum.NOT_FOUND.getCode()));
            throw new NotFoundException("暂无统计");
        }

        List<StatisticStudentCourse> studentCourses = queryStatisticStudentCourse(courseId, classId, null);
        if (CollectionUtils.isEmpty(studentCourses)) {
            response.addHeader("code-message", String.valueOf(ResultEnum.NOT_FOUND.getCode()));
            throw new NotFoundException("暂无统计");
        }

        List<StatisticStudentExam> studentExams = queryStatisticStudentExam(courseId, classId);
        Map<Long, List<StatisticStudentExam>> examMap = BeanUtil.aggByKeyToList("userId", studentExams);

        List<StatisticStudentTask> studentTasks = queryStatisticStudentTask(courseId, classId);
        Map<Long, List<StatisticStudentTask>> taskMap = BeanUtil.aggByKeyToList("userId", studentTasks);

        // 转换sheet基本信息
        List<String> basicInfos = convertBasicInfo(statistic, studentCourses.get(0).getCourseName(), studentExams);

        // 转换为每个单元sheet的title名称
        Integer examNum = CollectionUtils.isEmpty(statistic.getExamIds()) ? 0 : statistic.getExamIds().size();
        Table<Integer, Integer, List<Object>> tables = convertTitle(examNum);

        // 转换为每个单元sheet的列表数据
        List<List<String>> listData = convertList(studentCourses, examMap, taskMap, examNum);

        // excel下载
        StatisticExcelUtil.outputStreamForClassCourse(basicInfos, tables, listData,
                statistic.getClassName().trim() + "班级的课程统计", request, response);

    }

    @Override
    public StudentExamDTO queryCourseClassExam(Long courseId, Long classId, Long examId) {
        List<StatisticStudentExam> exams = queryStatisticStudentExam(courseId, classId, examId);
        if (CollectionUtils.isEmpty(exams)) {
            throw new NotFoundException("暂无考试统计");
        }
        return convertStudentExam(exams);
    }

    @Override
    public List<StatisticInfoDTO> queryStudentCourse(Long courseId, Long classId, UserInfoParam param) {
        List<StatisticStudentCourse> dbStatistic = studentCourseMapperExt.queryStatisticInfo(
                courseId, classId, param == null ? null : param.getUserInfo());
        if (CollectionUtils.isEmpty(dbStatistic)) {
            return null;
        }
        return convertStatisticInfoDTO(dbStatistic);
    }

    @Override
    public ExamDTO queryExamList(Long courseId, Long classId, Long examId, UserInfoParam param) {

        // 考试id集合
        List<Long> examIds = queryExamList(courseId, classId);

        // 获取考试用户基本信息
        String examName = "";
        List<ExamInfoDTO> examInfos = Lists.newArrayList();
        List<StatisticStudentExam> exams = queryStatisticStudentExam(courseId, classId, examId);
        if (CollectionUtils.isNotEmpty(exams)) {
            examName = exams.get(0).getName();
            if (param != null) {
                exams = exams.stream()
                        .filter(studentExam -> studentExam.getUserName().indexOf(param.getUserInfo()) != -1
                                || Objects.equals(studentExam.getUserNumber(), param.getUserInfo()))
                        .collect(Collectors.toList());
                examInfos = BeanUtil.batchTransform(ExamInfoDTO.class, exams);
            }
        }

        return ExamDTO.builder()
                .examIds(examIds).info(examInfos)
                .examName(examName).build();
    }

    @Override
    public List<Long> queryExamList(Long courseId, Long classId) {
        return studentExamMapperExt.queryExamList(courseId, classId);
    }

    @Override
    public List<TaskDTO> queryTaskList(Long courseId, Long classId, UserInfoParam param) {
        List<TaskDTO> taskDTOS = studentTaskMapperExt.queryTaskList(courseId, classId,
                param == null ? "" : param.getUserInfo());
        if (CollectionUtils.isEmpty(taskDTOS)) {
            return null;
        }
        taskDTOS.forEach(TaskDTO::computeAvgReportScore);

        return taskDTOS;
    }

    @Override
    public PageDTO<StudentTaskDTO> queryStudentTaskByPage(Integer pageNum, Integer pageSize,
                                                          Long courseId, Long classId, Long userId, Integer type) {
        PageHelper.startPage(pageNum, pageSize);
        List<StudentTaskDTO> studentTaskDTOS = studentTaskMapperExt.queryStudentTask(courseId, classId, userId, type);
        convertList(studentTaskDTOS);
        PageInfo<StudentTaskDTO> pageInfo = new PageInfo<>(studentTaskDTOS);
        return new PageDTO<>(pageInfo);
    }

    @Override
    public void downloadTask(Long courseId, Long classId, UserInfoParam param) {

        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                .getRequest();
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                .getResponse();

        // 获取统计班级信息
        List<StatisticClass> statisticClasses = queryStatisticClass(classId);
        if (CollectionUtils.isEmpty(statisticClasses)) {
            response.addHeader("code-message", String.valueOf(ResultEnum.NOT_FOUND.getCode()));
            throw new NotFoundException("暂无统计");
        }

        List<StatisticStudentCourse> studentCourses = queryStatisticStudentCourse(courseId, classId, null);
        if (CollectionUtils.isEmpty(studentCourses)) {
            response.addHeader("code-message", String.valueOf(ResultEnum.NOT_FOUND.getCode()));
            throw new NotFoundException("暂无统计");
        }

        String className = statisticClasses.get(0).getName();
        String courseName = studentCourses.get(0).getCourseName();

        List<StatisticStudentTask> studentTasks = queryStatisticStudentTask(courseId, classId);
        if (CollectionUtils.isEmpty(studentTasks)) {
            response.addHeader("code-message", String.valueOf(ResultEnum.NOT_FOUND.getCode()));
            throw new NotFoundException("暂无统计");
        }
        Map<Long, List<StatisticStudentTask>> taskMap = BeanUtil.aggByKeyToList("userId", studentTasks);

        List<Long> userIds = studentTasks.stream()
                .map(StatisticStudentTask::getUserId).distinct().collect(Collectors.toList());
        List<StatisticStudent> statisticStudents = queryStatisticStudent(userIds);
        statisticStudents = statisticStudents.stream()
                .filter(student -> student.getName().indexOf(param.getUserInfo()) != -1
                        || Objects.equals(student.getUserNumber(), param.getUserInfo())).collect(Collectors.toList());
        statisticStudents = sortStatisticStudent(statisticStudents, userIds);
        if (CollectionUtils.isEmpty(statisticStudents)) {
            response.addHeader("code-message", String.valueOf(ResultEnum.NOT_FOUND.getCode()));
            throw new NotFoundException("暂无统计");
        }

        // 转换为单元sheet名称
        List<String> sheetNames = convertSheetNames(statisticStudents);

        // 转换为每个单元sheet的基础数据 和 列表数据
        List<List<String>> basicData = Lists.newArrayList();
        List<List<List<String>>> listData = Lists.newArrayList();
        for (StatisticStudent statisticStudent : statisticStudents) {
            List<List<String>> list = Lists.newArrayList();
            List<StatisticStudentTask> studentTaskList = taskMap.get(statisticStudent.getUserId());
            int submittedNum = 0;
            long scores = 0l;
            long checkedNum = 0l;
            studentTaskList = studentTaskList == null ? Lists.newArrayList() : studentTaskList;

            // 排序
            studentTaskList.sort(Comparator.comparingInt(StatisticStudentTask::getOrderNum));

            for (int i = 0; i < studentTaskList.size(); i++) {
                StatisticStudentTask studentTask = studentTaskList.get(i);
                List<String> taskData = Lists.newArrayList((i + 1) + "", studentTask.getName(),
                        !studentTask.getOpened() ? "未公开" : "已公开",
                        getStatusStr(studentTask.getReportSubmitted(), studentTask.getReportCheck()),
                        studentTask.getReportCheck() && studentTask.getReportScore() != null ?
                                String.valueOf(studentTask.getReportScore()) : "-");
                list.add(taskData);
                if (studentTask.getReportSubmitted()) {
                    submittedNum++;
                    scores = scores + (studentTask.getReportScore() == null ? 0 : studentTask.getReportScore());
                    if (studentTask.getReportCheck()) {
                        checkedNum++;
                    }
                }
            }

            // 2021 新增平均值
            String avgScore = "0";
            if(scores != 0 && checkedNum != 0){
                avgScore = NumberUtil.round(NumberUtil.div(scores, checkedNum), 2).toPlainString();
            }

            List<String> data = Lists.newArrayList(className, courseName, "实验报告",
                    statisticStudent.getName(), statisticStudent.getUserNumber(),
                    String.valueOf(submittedNum), avgScore, "章节/任务名称");
            basicData.add(data);
            listData.add(list);
        }

        // excel下载
        StatisticExcelUtil.outputStreamForStudentLearn(sheetNames, basicData, listData,
                className + "班级的课程实验报告统计", request, response);
    }

    @Override
    public List<ChapterDTO> queryChapterList(Long courseId, Long classId, UserInfoParam param) {
        List<ChapterDTO> chapterDTOS = studentChapterMapperExt.queryChapterList(courseId, classId,
                param == null ? "" : param.getUserInfo());
        if (CollectionUtils.isEmpty(chapterDTOS)) {
            return null;
        }
        chapterDTOS.forEach(ChapterDTO::computeAvgPracticeScore);
        return chapterDTOS;
    }

    @Override
    public PageDTO<StudentChapterDTO> queryStudentChapterByPage(Integer pageNum, Integer pageSize,
                                                                Long courseId, Long classId, Long userId) {
        PageHelper.startPage(pageNum, pageSize);
        List<StudentChapterDTO> studentChapterDTOS = studentChapterMapperExt.queryStudentChapter(
                courseId, classId, userId);
        convertChapterList(studentChapterDTOS);
        PageInfo<StudentChapterDTO> pageInfo = new PageInfo<>(studentChapterDTOS);
        return new PageDTO<>(pageInfo);
    }

    @Override
    public void downloadChapter(Long courseId, Long classId, UserInfoParam param) {

        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                .getRequest();
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                .getResponse();

        // 获取统计班级信息
        List<StatisticClass> statisticClasses = queryStatisticClass(classId);
        if (CollectionUtils.isEmpty(statisticClasses)) {
            response.addHeader("code-message", String.valueOf(ResultEnum.NOT_FOUND.getCode()));
            throw new NotFoundException("暂无统计");
        }

        List<StatisticStudentCourse> studentCourses = queryStatisticStudentCourse(courseId, classId, null);
        if (CollectionUtils.isEmpty(studentCourses)) {
            response.addHeader("code-message", String.valueOf(ResultEnum.NOT_FOUND.getCode()));
            throw new NotFoundException("暂无统计");
        }

        String className = statisticClasses.get(0).getName();
        String courseName = studentCourses.get(0).getCourseName();

        List<StatisticStudentChapter> studentChapters = queryStatisticStudentChapter(courseId, classId, null);
        if (CollectionUtils.isEmpty(studentChapters)) {
            response.addHeader("code-message", String.valueOf(ResultEnum.NOT_FOUND.getCode()));
            throw new NotFoundException("暂无统计");
        }
        Map<Long, List<StatisticStudentChapter>> chapterMap = BeanUtil.aggByKeyToList("userId", studentChapters);

        List<Long> userIds = studentChapters.stream()
                .map(StatisticStudentChapter::getUserId).distinct().collect(Collectors.toList());
        List<StatisticStudent> statisticStudents = queryStatisticStudent(userIds);
        statisticStudents = statisticStudents.stream()
                .filter(student -> student.getName().indexOf(param.getUserInfo()) != -1
                        || Objects.equals(student.getUserNumber(), param.getUserInfo())).collect(Collectors.toList());
        statisticStudents = sortStatisticStudent(statisticStudents, userIds);
        if (CollectionUtils.isEmpty(statisticStudents)) {
            response.addHeader("code-message", String.valueOf(ResultEnum.NOT_FOUND.getCode()));
            throw new NotFoundException("暂无统计");
        }

        // 转换为单元sheet名称
        List<String> sheetNames = convertSheetNames(statisticStudents);

        // 转换为每个单元sheet的基础数据 和 列表数据
        List<List<String>> basicData = Lists.newArrayList();
        List<List<List<String>>> listData = Lists.newArrayList();
        for (StatisticStudent statisticStudent : statisticStudents) {
            List<List<String>> list = Lists.newArrayList();
            List<StatisticStudentChapter> studentChapterList = chapterMap.get(statisticStudent.getUserId());
            int submittedNum = 0;
            long scores = 0l;
            long checkNum = 0l;
            studentChapterList = studentChapterList == null ? Lists.newArrayList() : studentChapterList;

            // 排序
            studentChapterList.sort(Comparator.comparingInt(StatisticStudentChapter::getOrderNum));

            for (int i = 0; i < studentChapterList.size(); i++) {
                StatisticStudentChapter studentChapter = studentChapterList.get(i);
                List<String> taskData = Lists.newArrayList((i + 1) + "", studentChapter.getName(),
                        !studentChapter.getOpened() ? "未公开" : "已公开",
                        getStatusStr(studentChapter.getPracticeSubmitted(), studentChapter.getPracticeCheck()),
                        studentChapter.getPracticeCheck() && studentChapter.getPracticeScore() != null ?
                                String.valueOf(studentChapter.getPracticeScore()) : "-");
                list.add(taskData);
                if (studentChapter.getPracticeSubmitted()) {
                    submittedNum++;
                    scores = scores + (studentChapter.getPracticeScore() == null ?
                            0 : studentChapter.getPracticeScore());
                    if (studentChapter.getPracticeCheck()) {
                        checkNum++;
                    }
                }
            }

            String avgScore = "0";
            if( scores != 0 &&  checkNum > 0 ){
                avgScore = NumberUtil.round(NumberUtil.div(scores, checkNum), 2).toPlainString();
            }

            List<String> data = Lists.newArrayList(className, courseName, "随堂练习",
                    statisticStudent.getName(), statisticStudent.getUserNumber(),
                    String.valueOf(submittedNum), String.valueOf(avgScore), "章节名称");
            basicData.add(data);
            listData.add(list);
        }

        // excel下载
        StatisticExcelUtil.outputStreamForStudentLearn(sheetNames, basicData, listData,
                className + "班级的课程随堂练习统计", request, response);
    }

    @Override
    public PageDTO<UserInfoDTO> listStudentByPage(Integer pageNum, Integer pageSize, Long classId, Long courseId) {

        // 获取班级基本信息
        Class classInfo = classService.queryClass(classId);

        // 获取班级下的学生分页
        PageHelper.startPage(pageNum, pageSize);
        List<UserInfoDTO> userInfos = studentCourseMapperExt.queryUser(courseId, classId);
        convertStatisticStudent(userInfos, classInfo);
        PageInfo<UserInfoDTO> pageInfo = new PageInfo<>(userInfos);
        return new PageDTO<>(pageInfo);
    }

    @Override
    public void downloadStudentStatistic(Long classId, Long courseId) {

        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                .getRequest();
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                .getResponse();

        // 获取统计班级信息
        List<StatisticClass> statisticClasses = queryStatisticClass(classId);
        if (CollectionUtils.isEmpty(statisticClasses)) {
            response.addHeader("code-message", String.valueOf(ResultEnum.NOT_FOUND.getCode()));
            throw new NotFoundException("暂无统计");
        }

        // 统计班级下的学生信息
        List<StatisticStudentCourse> studentCourses = queryStatisticStudentCourse(courseId, classId, null);
        if (CollectionUtils.isEmpty(studentCourses)) {
            response.addHeader("code-message", String.valueOf(ResultEnum.NOT_FOUND.getCode()));
            throw new NotFoundException("暂无统计");
        }
        List<Long> userIds = studentCourses.stream()
                .map(StatisticStudentCourse::getUserId).distinct().collect(Collectors.toList());

        // 获取学生的课程信息
        studentCourses = queryStatisticStudentCourse(
                null, null, userIds, "entry_time ASC , id ASC");

        List<StatisticStudent> statisticStudents = queryStatisticStudent(userIds);
        statisticStudents = sortStatisticStudent(statisticStudents, userIds);
        if (CollectionUtils.isEmpty(statisticStudents)) {
            response.addHeader("code-message", String.valueOf(ResultEnum.NOT_FOUND.getCode()));
            throw new NotFoundException("暂无统计");
        }

        // 转换为单元sheet名称
        List<String> sheetNames = convertSheetNames(statisticStudents);

        // 转换为每个单元sheet的基础数据
        List<List<String>> basicInfos = convertBasicInfo(statisticStudents, statisticClasses.get(0).getName());

        // 获取学生的课程、考试信息、任务信息
        List<StatisticStudentExam> studentExams = queryStatisticStudentExam(classId, userIds,
                "exam_time ASC");
        List<StatisticStudentTask> studentTasks = queryStatisticStudentTask(classId, userIds);

        Map<Long, List<StatisticStudentCourse>> userCourseMap = BeanUtil.aggByKeyToList("userId", studentCourses);
        Map<Long, List<StatisticStudentExam>> userExamMap = BeanUtil.aggByKeyToList("userId", studentExams);
        Map<Long, List<StatisticStudentTask>> taskMap = BeanUtil.aggByKeyToList("userId", studentTasks);

        // 转换为每个单元sheet的title名称
        List<Table<Integer, Integer, List<Object>>> tables = convertTable(statisticStudents, userExamMap);

        // 转换为每个单元sheet的列表数据
        List<List<List<String>>> listData = convertList(statisticStudents, userCourseMap, userExamMap, taskMap);

        // excel下载
        StatisticExcelUtil.outputStreamForStudent(sheetNames, basicInfos, tables, listData,
                statisticClasses.get(0).getName() + "班级的学生统计", request, response);

    }

    @Override
    public StduentDTO getStudentStatistic(Long classId, Long userId, Long courseId) {
        // 获取统计班级信息
        List<StatisticClass> statisticClasses = queryStatisticClass(classId);
        if (CollectionUtils.isEmpty(statisticClasses)) {
            throw new NotFoundException("暂无统计");
        }

        // 获取学生的课程
        List<StatisticStudentCourse> studentCourses = queryStatisticStudentCourse(null, null,
                Lists.newArrayList(userId), "entry_time ASC , id ASC");
        if (CollectionUtils.isEmpty(studentCourses)) {
            throw new NotFoundException("暂无统计");
        }

        // 获取班级的所有学生
        List<StatisticStudentCourse> classCourses = queryStatisticStudentCourse(courseId, classId, null);
        List<Long> userIds = classCourses.stream()
                .map(StatisticStudentCourse::getUserId).distinct().collect(Collectors.toList());

        // 统计学生信息
        StatisticStudent statisticStudent = getStatisticStudent(userId);
        List<StatisticStudentCourse> userCourses = studentCourses.stream()
                .filter(data -> Objects.equals(data.getUserId(), userId)).collect(Collectors.toList());
        return convert(statisticStudent, statisticClasses.get(0), userIds, userCourses);
    }

    @Override
    public StatisticDTO getStudentCourseStatistic(Long courseId, Long classId, Long userId) {

        // 时长统计、提交统计、考勤统计、问题交流统计、随堂提问统计
        List<StatisticStudentCourse> studentStatistic = queryStatisticStudentCourse(courseId, classId, userId);
        if (CollectionUtils.isEmpty(studentStatistic)) {
            throw new NotFoundException("暂无统计");
        }

        // 实验通过统计
        List<StatisticStudentTask> taskStatistic = queryStatisticStudentTask(courseId, classId, userId);

        // 考试统计
        List<StatisticStudentExam> studentExams = listStudentExam(courseId, classId, userId,
                "exam_time ASC");

        return convertStatisticDTO(studentStatistic.get(0), taskStatistic, studentExams);
    }

    @Override
    public StduentDTO getStudentStatistic(Long userId) {

        // 统计学生
        StatisticStudent statisticStudent = getStatisticStudent(userId);
        if (statisticStudent == null) {
            throw new NotFoundException("暂无统计");
        }

        // 获取学生的课程
        List<StatisticStudentCourse> studentCourses = queryStatisticStudentCourse(null, null,
                Lists.newArrayList(userId), "entry_time ASC , id ASC");
        if (CollectionUtils.isEmpty(studentCourses)) {
            throw new NotFoundException("暂无统计");
        }

        return convert(statisticStudent, studentCourses);
    }

    @Override
    public void settingFullMark(SettingMarkPO settingMarkPO) {

        String sysFunkKey = LikedSysFunkEnum.CLASSROOM_PERFORMANCE.getSysFunkKey();
        List<Long> classIdList = settingMarkPO.getClassIdList();

        settingMarkPO.setBusiType(sysFunkKey);
        // 获取最大分数值进行比较
        Integer maxManifestScore = studentCourseMapperExt.getMaxManifestScore(settingMarkPO.getCourseId(), classIdList.get(0));
        if(ObjectUtil.isNotNull(maxManifestScore)){

             if(maxManifestScore.compareTo(settingMarkPO.getFullMark()) == 1){
                 throw new GlobalException(ResultEnum.ERROR_FULL_MARK_CANTLESS_SCORE);
             }
        }

        concludeBusiFullMarkService.settingFullMark(settingMarkPO, null);

    }


    @Override
    public void settingScore(Long id, Integer score) {

        if(ObjectUtil.isNull(id)||ObjectUtil.isNull(score)){
            throw new GlobalException(ResultEnum.BAD_REQUEST);
        }
        // 查询当前分数
        StatisticStudentCourseExample statisticStudentCourseExample = new StatisticStudentCourseExample();
        statisticStudentCourseExample.createCriteria().andIdEqualTo(id);
        List<StatisticStudentCourse> statisticStudentCourses = statisticStudentCourseMapper.selectByExample(statisticStudentCourseExample);

        // 未查询到关联记录
        if(CollectionUtil.isEmpty(statisticStudentCourses)){

            throw new GlobalException(ResultEnum.NOT_FOUND_REF_RECORD);
        }
        StatisticStudentCourse statisticStudentCourse = statisticStudentCourses.get(0);
        SettingMarkPO settingMarkPO = new SettingMarkPO();

        settingMarkPO.setBusiType(LikedSysFunkEnum.CLASSROOM_PERFORMANCE.getSysFunkKey());
        settingMarkPO.setClassIdList(Lists.newArrayList(statisticStudentCourse.getClassId()));
        settingMarkPO.setCourseId(statisticStudentCourse.getCourseId());

        BusiFullMarkExceedDTO exceedFullMark = concludeBusiFullMarkService.isExceedFullMark(settingMarkPO, score);

        if(exceedFullMark.isExceedFullMark()){
            throw new GlobalException(ResultEnum.ERROR_FULL_MARK_THEN_SCORE);
        }
        // 更新到表
        StatisticStudentCourse updateRecord = new StatisticStudentCourse();
        updateRecord.setId(id);
        updateRecord.setManifestScore(score);
        statisticStudentCourseMapper.updateByPrimaryKeySelective(updateRecord);

    }

    @Override
    public void exportData(Long courseId, Long classId, UserInfoParam param,
                           HttpServletRequest request, HttpServletResponse response) {

        List<StatisticInfoDTO> data = queryStudentCourse(courseId, classId, param);
        CourseBasicDTO course = courseService.getCourse(courseId);
        // 创建头文件
        //通过工具类创建writer
        HSSFWorkbook workbook = new HSSFWorkbook();
        // 创建sheet页
        HSSFSheet sheet = workbook.createSheet();

        CellStyle cellStyle = CellStyleUtils.createCellStyle(workbook);
        // 头样式信息
        CellStyle secondHeaderStyle = CellStyleUtils.creadeSecondHeaderStyle(workbook);
        Font secondFont= workbook.createFont();
        secondFont.setFontHeightInPoints((short) 14);
        secondFont.setFontName("宋体");
        secondHeaderStyle.setFont(secondFont);

        // 1.创建头信息
        String courseName = "课程：" + course.getName() + "  " + "随堂提问统计";
        CellStyle titleStyle = CellStyleUtils.universalTitleStyle(workbook);

        List<String> headerColumns = Lists.newArrayList("序号", "学生姓名", "学生学号" ,"简答题回答次数", "简答题表现", "判断题回答次数", "判断题表现", "得分");

        // 简答题表现有三列  判断题表现有两列
        int countColumSize = headerColumns.size() + 2 + 1;

        HSSFRow row0 = sheet.createRow(0); // 列头样式
        HSSFRow row1 = sheet.createRow(1); // 列头样式
        HSSFRow row2 = sheet.createRow(2); // 列头样式
        for (int i = 0; i < countColumSize; i++) {

            HSSFCell row0cell = row0.createCell(i);
            row0cell.setCellStyle(titleStyle);

            HSSFCell row1cell = row1.createCell(i);
            row1cell.setCellStyle(secondHeaderStyle);

            HSSFCell row2cell = row2.createCell(i);
            row2cell.setCellStyle(secondHeaderStyle);

        }

        int columnPointer = 0;
        // 初始化第一个title
        CellUtil.mergingCells(sheet, 0, 0 ,0, countColumSize - 1, titleStyle);
        row0.getCell(0).setCellValue(courseName);
        row0.getCell(0).setCellStyle(titleStyle);
        row0.setHeight((short) 800);

        // 2. 初始化两行
        for (int i = 0; i < headerColumns.size(); i++) {

            String content = headerColumns.get(i);

            // 简答题表现单独处理
            if(content.equals("简答题表现")){

                CellUtil.mergingCells(sheet, 1, 1 ,columnPointer,columnPointer+2, secondHeaderStyle);
                row1.getCell(columnPointer).setCellValue(content);
                row2.getCell(columnPointer).setCellValue("优");
                row2.getCell(++columnPointer).setCellValue("良");
                row2.getCell(++columnPointer).setCellValue("差");
                columnPointer+=1;
            }else if(content.equals("判断题表现")){

                CellUtil.mergingCells(sheet, 1, 1, columnPointer, columnPointer+1, secondHeaderStyle);
                row1.getCell(columnPointer).setCellValue(content);
                row2.getCell(columnPointer).setCellValue("正确");
                row2.getCell(++columnPointer).setCellValue("错误");
                columnPointer+=1;
            }else{
                CellUtil.mergingCells(sheet, 1, 2, columnPointer, columnPointer , secondHeaderStyle);
                row1.getCell(columnPointer).setCellValue(content);
                columnPointer++;
            }

        }
        // 3. 装载数据
        int dataRow = 3;

        if( ObjectUtil.isNotNull(data) ){

            for (int i = 0; i < data.size(); i++) {

                StatisticInfoDTO statisticInfoDTO = data.get(i);
                HSSFRow row = sheet.createRow(dataRow);
                for (int j = 0; j < countColumSize; j++) {
                    HSSFCell cell = row.createCell(j);
                    cell.setCellStyle(cellStyle);
                }
                // 序号
                row.getCell(0).setCellValue(i + 1);
                // 学生姓名
                row.getCell(1).setCellValue(statisticInfoDTO.getName());
                // 学号
                row.getCell(2).setCellValue(statisticInfoDTO.getUserNumber());
                // 简答题次数
                row.getCell(3).setCellValue(statisticInfoDTO.getQuestionAnwserNum());
                // 优
                row.getCell(4).setCellValue(statisticInfoDTO.getQuestionAnwserType1Num());
                // 良
                row.getCell(5).setCellValue(statisticInfoDTO.getQuestionAnwserType2Num());
                // 差
                row.getCell(6).setCellValue(statisticInfoDTO.getQuestionAnwserType3Num());
                // 判断题回答次数
                row.getCell(7).setCellValue(statisticInfoDTO.getChoiceAnwserNum());
                // 正确
                row.getCell(8).setCellValue(statisticInfoDTO.getChoiceAnwserType1Num());
                // 错误
                row.getCell(9).setCellValue(statisticInfoDTO.getChoiceAnwserType2Num());
                // 得分
                if(ObjectUtil.isNull(statisticInfoDTO.getManifestScore())){
                    row.getCell(10).setCellValue("");
                }else{
                    row.getCell(10).setCellValue(statisticInfoDTO.getManifestScore());
                }
                dataRow++;
            }
        }


        try{
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            workbook.write(os);
            StatisticExcelUtil.ExcelOutputStream("downLoadSubScoreTemplate", request, response, os);
            workbook.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }


    /**
     * 获取满分
     * @param courseId
     * @param classId
     * @return
     */
    @Override
    public Integer getfullmark(Long courseId, Long classId) {

        BusiFullMark busiFullMark = busiFullMarkMapperHelper.get(LikedSysFunkEnum.CLASSROOM_PERFORMANCE.getSysFunkKey(), courseId, classId, null);
        return busiFullMark.getFullMark();

    }


    private Map<Long, Integer> queryStudentCourse(List<Long> studentIds) {

        // 获取用户的班级
        List<UserClass> userClasses = userService.queryUserClass(studentIds);
        Map<Long, List<UserClass>> userClassMap = BeanUtil.aggByKeyToList("userId", userClasses);

        List<Long> classIds = userClasses.stream().map(UserClass::getClassId).distinct().collect(Collectors.toList());

        // 根据班级id集合获取未删除的课程信息
        List<ClassCourseDTO> classCourses = classService.queryClassCourse(classIds);
        Map<Long, List<ClassCourseDTO>> classMap = BeanUtil.aggByKeyToList("id", classCourses);

        Map<Long, Integer> map = Maps.newHashMap();
        userClassMap.forEach((userId, userClassList) -> {
            Integer num = 0;
            for (UserClass userClass : userClassList) {
                List<ClassCourseDTO> classCourseDTOS = classMap.get(userClass.getClassId());
                if (CollectionUtils.isNotEmpty(classCourseDTOS)) {
                    num = num + classCourseDTOS.size();
                }
            }
            map.put(userId, num);
        });

        return map;
    }

    private void insertOrUpdateStudent(List<Long> dbStudentIds, Map<Long, UserInfoDTO> userMap) {

        if (CollectionUtils.isEmpty(dbStudentIds)) return;

        // 获取现在数据库登陆次数
        List<Operation> operationList = operationService.listOperation(dbStudentIds, null, EventCodeEnum.LOGIN);
        Map<Long, List<Operation>> operationMap = BeanUtil.aggByKeyToList("userId", operationList);

        // 学生的课程
        Map<Long, Integer> userClassMap = queryStudentCourse(dbStudentIds);

        // 学生的学习时长
        List<StatisticStudentCourse> studentCourses = studentCourseMapperExt.queryStatisticTime(dbStudentIds);
        Map<Long, StatisticStudentCourse> timeMap = BeanUtil.mapByKey("userId", studentCourses);

        List<StatisticStudent> students = queryStatisticStudent(dbStudentIds);
        Map<Long, StatisticStudent> studentMap = BeanUtil.mapByKey("userId", students);
        List<StatisticStudent> studentStatistic = Lists.newArrayList();
        for (Long userId : dbStudentIds) {
            StatisticStudent student = new StatisticStudent();
            StatisticStudent statisticStudent = studentMap.get(userId);
            if (statisticStudent != null) {
                student.setId(statisticStudent.getId());
            }
            UserInfoDTO userInfo = userMap.get(userId);
            if (userInfo == null) {
                continue;
            }
            student.setUserId(userId);
            student.setUserNumber(userInfo.getUserNumber());
            student.setName(userInfo.getName());
            student.setIcon(userInfo.getIcon());
            student.setRegTime(userInfo.getCreateTime());
            student.setCourseNum(0);
            student.setLoginNum(0);
            student.setLearnTime(0L);

            if (MapUtils.isNotEmpty(userClassMap) && userClassMap.containsKey(userId)) {
                student.setCourseNum(userClassMap.get(userId));
            }
            if (MapUtils.isNotEmpty(operationMap) && operationMap.containsKey(userId)) {
                student.setLoginNum(operationMap.get(userId).size());
            }
            if (MapUtils.isNotEmpty(timeMap) && timeMap.containsKey(userId)) {
                StatisticStudentCourse studentCourse = timeMap.get(userId);
                student.setLearnTime(new BigDecimal(studentCourse.getExperimentTime())
                        .add(new BigDecimal(studentCourse.getTheoryTime())).longValue());
            }
            studentStatistic.add(student);
        }

        List<StatisticStudent> addData = studentStatistic.stream()
                .filter(statisticStudent -> statisticStudent.getId() == null).collect(Collectors.toList());
        if (CollectionUtils.isNotEmpty(addData)) {
            studentMapperExt.insertBatch(addData);
        }

        List<StatisticStudent> updateData = studentStatistic.stream()
                .filter(statisticStudent -> statisticStudent.getId() != null).collect(Collectors.toList());
        if (CollectionUtils.isNotEmpty(updateData)) {
            studentMapperExt.updateBatch(updateData);
        }

    }

    private List<StatisticStudentChapter> insertOrUpdateChapter(Long courseId, Long classId, List<Long> dbStudentIds,
                                                                Map<Long, UserInfoDTO> userMap, List<Long> dbChapterIds,
                                                                Map<Long, ChapterBasicInfoDTO> chapterMap) {

        List<StatisticStudentChapter> chapterStatistic = Lists.newArrayList();
        if (CollectionUtils.isEmpty(dbChapterIds) || MapUtils.isEmpty(chapterMap)) return chapterStatistic;

        // 过滤章节不带随堂练习的
        List<Long> practiceChapterIds = questionRepositoryService.findHavePracticeChapterIds(dbChapterIds);
        if (CollectionUtils.isEmpty(practiceChapterIds)) return chapterStatistic;
        List<Long> chapterIds = dbChapterIds.stream()
                .filter(chapterId -> practiceChapterIds.contains(chapterId)).collect(Collectors.toList());

        // 获取现在数据库的随堂练习情况
        List<PrictceCourseStatisticsDTO> practices = practiceService.findPracticeByChapterIdsAndUserIds(
                chapterIds, dbStudentIds);
        if (CollectionUtils.isEmpty(practices)) return chapterStatistic;
        Map<Long, Map<Long, List<PrictceCourseStatisticsDTO>>> chpaterMap = practices.stream()
                .collect(Collectors.groupingBy(PrictceCourseStatisticsDTO::getUserId,
                        Collectors.groupingBy(PrictceCourseStatisticsDTO::getChapterId)));


        // 获取统计表中数据
        List<StatisticStudentChapter> studentChapters = queryStatisticStudentChapter(courseId, classId, null);
        Map<Long, Map<Long, List<StatisticStudentChapter>>> userChapterMap = studentChapters.stream()
                .collect(Collectors.groupingBy(StatisticStudentChapter::getUserId,
                        Collectors.groupingBy(StatisticStudentChapter::getChapterId)));

        for (Long userId : dbStudentIds) {

            for (int i = 0; i < chapterIds.size(); i++) {
                Long chapterId = chapterIds.get(i);

                if (MapUtils.isEmpty(chpaterMap) || !chpaterMap.containsKey(userId)
                        || !chpaterMap.get(userId).containsKey(chapterId)) continue;

                PrictceCourseStatisticsDTO practice = chpaterMap.get(userId).get(chapterId).get(0);
                StatisticStudentChapter chapter = new StatisticStudentChapter();
                chapter.setOrderNum(i + 1);

                // 原先统计如果存在，将id进行赋值，其他数据重新覆盖
                if (MapUtils.isNotEmpty(userChapterMap) && userChapterMap.containsKey(practice.getUserId())
                        && userChapterMap.get(practice.getUserId()).containsKey(practice.getChapterId())) {
                    chapter.setId(userChapterMap.get(practice.getUserId()).get(practice.getChapterId()).get(0).getId());
                }

                chapter.setChapterId(practice.getChapterId());
                chapter.setCourseId(courseId);
                chapter.setClassId(classId);
                chapter.setUserId(practice.getUserId());
                chapter.setUserName(userMap.get(practice.getUserId()).getName());
                chapter.setUserNumber(userMap.get(practice.getUserId()).getUserNumber());
                chapter.setName(chapterMap.get(practice.getChapterId()).getName());
                chapter.setPracticeSubmitted(practice.getSubmitStatus());
                chapter.setPracticeCheck(practice.getCheck() == null ? false : practice.getCheck());
                chapter.setPracticeScore(practice.getScore() == null ? 0 : practice.getScore());
                chapter.setPracticeErrRate(0f);
                if (practice.getObjectiveItemCount() != null && practice.getObjectiveItemCount() > 0) {
                    chapter.setPracticeErrRate(new BigDecimal(practice.getObjectiveItemErrorCount())
                            .divide(new BigDecimal(practice.getObjectiveItemCount()), 4, BigDecimal.ROUND_HALF_UP)
                            .multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP)
                            .floatValue());
                }
                chapter.setOpened(Objects.equals(chapterMap.get(practice.getChapterId()).getFalg(), 0) ?
                        true : false);
                chapterStatistic.add(chapter);
            }
        }

        if (CollectionUtils.isEmpty(chapterStatistic)) return null;
        insertBatchChapter(chapterStatistic.stream().filter(data -> data.getId() == null).collect(Collectors.toList()));
        updateBatchChapter(chapterStatistic.stream().filter(data -> data.getId() != null).collect(Collectors.toList()));

        return chapterStatistic;
    }

    private void updateBatchChapter(List<StatisticStudentChapter> updateData) {
        if (CollectionUtils.isEmpty(updateData)) return;

        int length = updateData.size();
        int i = 0;
        while (length > 100) {
            studentChapterMapperExt.updateBatch(updateData.subList(i, i + 100));
            i = i + 100;
            length = length - 100;
        }
        if (length > 0) {
            studentChapterMapperExt.updateBatch(updateData.subList(i, i + length));
        }
    }

    private void insertBatchChapter(List<StatisticStudentChapter> addData) {
        if (CollectionUtils.isEmpty(addData)) return;

        int length = addData.size();
        int i = 0;
        while (length > 100) {
            studentChapterMapperExt.insertBatch(addData.subList(i, i + 100));
            i = i + 100;
            length = length - 100;
        }
        if (length > 0) {
            studentChapterMapperExt.insertBatch(addData.subList(i, i + length));
        }
    }

    private List<StatisticStudentTask> insertOrUpdateTask(Long courseId, Long classId, List<Long> dbStudentIds,
                                                          Map<Long, UserInfoDTO> userMap, List<Long> dbTaskIds,
                                                          Map<Long, ChapterBasicInfoDTO> chapterMap) {

        List<StatisticStudentTask> taskStatistic = Lists.newArrayList();
        if (CollectionUtils.isEmpty(dbTaskIds) || MapUtils.isEmpty(chapterMap)) return taskStatistic;

        List<Long> imageTasks = imageAllocationService.getHaveImageTasks(dbTaskIds);

        // 获取现在数据库的实验报告情况
        List<ExperimentReport> reports = experimentReportService.findReportByUserIdsAndTaskIds(dbStudentIds, dbTaskIds);
        Map<Long, Map<Long, List<ExperimentReport>>> reportMap = reports.stream()
                .collect(Collectors.groupingBy(ExperimentReport::getUserId,
                        Collectors.groupingBy(ExperimentReport::getChapterId)));

        // 获取现在数据库的实验通过情况
        Map<Long, Map<Long, List<ExperimentStatisticsTaskPassDTO>>> passMap = Maps.newHashMap();
        if (CollectionUtils.isNotEmpty(imageTasks)) {
            List<ExperimentStatisticsTaskPassDTO> experimentPassList = experimentPassService.findExperimentByUserId(
                    courseId, classId, imageTasks, dbStudentIds);
            passMap = experimentPassList.stream()
                    .collect(Collectors.groupingBy(ExperimentStatisticsTaskPassDTO::getUserId,
                            Collectors.groupingBy(ExperimentStatisticsTaskPassDTO::getTaskId)));
        }

        // 获取统计表中数据
        List<StatisticStudentTask> studentTasks = queryStatisticStudentTask(courseId, classId);
        Map<Long, Map<Long, List<StatisticStudentTask>>> userTaskMap = studentTasks.stream()
                .collect(Collectors.groupingBy(StatisticStudentTask::getUserId,
                        Collectors.groupingBy(StatisticStudentTask::getTaskId)));

        for (Long userId : dbStudentIds) {

            for (int i = 0; i < dbTaskIds.size(); i++) {
                Long taskId = dbTaskIds.get(i);

                StatisticStudentTask task = new StatisticStudentTask();
                task.setOrderNum(i + 1);

                // 原先统计如果存在，将id进行赋值，其他数据重新覆盖
                if (MapUtils.isNotEmpty(userTaskMap) && userTaskMap.containsKey(userId)
                        && userTaskMap.get(userId).containsKey(taskId)) {
                    task.setId(userTaskMap.get(userId).get(taskId).get(0).getId());
                }

                UserInfoDTO userInfo = userMap.get(userId);

                ExperimentReport experimentReport = null;
                ExperimentStatisticsTaskPassDTO passDTO = null;
                if (MapUtils.isNotEmpty(reportMap) && reportMap.containsKey(userId)
                        && reportMap.get(userId).containsKey(taskId)) {
                    experimentReport = reportMap.get(userId).get(taskId).get(0);
                }
                if (MapUtils.isNotEmpty(passMap) && passMap.containsKey(userId)
                        && passMap.get(userId).containsKey(taskId)) {
                    passDTO = passMap.get(userId).get(taskId).get(0);
                    if (passDTO.getPass() == null) {
                        passDTO.setPass(false);
                    }
                }
                task.setClassId(classId);
                task.setCourseId(courseId);
                task.setUserId(userId);
                task.setTaskId(taskId);
                task.setUserName(userInfo.getName());
                task.setUserNumber(userInfo.getUserNumber());
                task.setName(chapterMap.get(taskId).getName());
                task.setOpened(Objects.equals(chapterMap.get(chapterMap.get(taskId).getParentId()).getFalg(), 0) ?
                        true : false);
                task.setExperimentPassed(passDTO == null || passDTO.getPass() == null ? null : passDTO.getPass());
                boolean reportSubmitted = false;
                if (experimentReport != null
                        && (experimentReport.getDocCommit() != null && experimentReport.getDocCommit()
                        || experimentReport.getOnlineCommit() != null && experimentReport.getOnlineCommit())) {
                    reportSubmitted = true;
                }
                task.setReportSubmitted(reportSubmitted);

                boolean reportChecked = false;
                if (experimentReport != null && experimentReport.getCheck() != null && experimentReport.getCheck()) {
                    reportChecked = true;
                }
                task.setReportCheck(reportChecked);
                task.setReportScore(experimentReport == null || experimentReport.getScore() == null ?
                        0 : experimentReport.getScore());

                taskStatistic.add(task);
            }

        }

        insertBatchTask(taskStatistic.stream().filter(data -> data.getId() == null).collect(Collectors.toList()));
        updateBatchTask(taskStatistic.stream().filter(data -> data.getId() != null).collect(Collectors.toList()));

        return taskStatistic;
    }

    private void insertBatchTask(List<StatisticStudentTask> taskStatistic) {
        if (CollectionUtils.isEmpty(taskStatistic)) return;

        int length = taskStatistic.size();
        int i = 0;
        while (length > 100) {
            studentTaskMapperExt.insertBatch(taskStatistic.subList(i, i + 100));
            i = i + 100;
            length = length - 100;
        }
        if (length > 0) {
            studentTaskMapperExt.insertBatch(taskStatistic.subList(i, i + length));
        }
    }

    private void updateBatchTask(List<StatisticStudentTask> taskStatistic) {
        if (CollectionUtils.isEmpty(taskStatistic)) return;

        int length = taskStatistic.size();
        int i = 0;
        while (length > 100) {
            studentTaskMapperExt.updateBatch(taskStatistic.subList(i, i + 100));
            i = i + 100;
            length = length - 100;
        }
        if (length > 0) {
            studentTaskMapperExt.updateBatch(taskStatistic.subList(i, i + length));
        }
    }

    private void insertOrUpdateExam(Long courseId, Long classId, List<Long> dbStudentIds,
                                    Map<Long, UserInfoDTO> userMap, List<UserCourseTestInfoDTO> dbExams) {
        List<StatisticStudentExam> examStatistic = Lists.newArrayList();
        if (CollectionUtils.isEmpty(dbExams)) return;

        Map<Long, List<UserCourseTestInfoDTO>> dbExamMap = BeanUtil.aggByKeyToList("userId", dbExams);

        List<StatisticStudentExam> studentExams = queryStatisticStudentExam(courseId, classId);
        Map<Long, Map<Long, List<StatisticStudentExam>>> examMap = studentExams.stream()
                .collect(Collectors.groupingBy(StatisticStudentExam::getUserId,
                        Collectors.groupingBy(StatisticStudentExam::getExamId)));

        for (Long dbStudentId : dbStudentIds) {

            if (MapUtils.isEmpty(dbExamMap) || !dbExamMap.containsKey(dbStudentId)) continue;

            List<UserCourseTestInfoDTO> exams = dbExamMap.get(dbStudentId);
            if (CollectionUtils.isEmpty(exams)) continue;

            for (UserCourseTestInfoDTO dbExam : exams) {
                StatisticStudentExam exam = new StatisticStudentExam();

                if (MapUtils.isNotEmpty(examMap) && examMap.containsKey(dbExam.getUserId())
                        && examMap.get(dbExam.getUserId()).containsKey(dbExam.getId())) {
                    exam.setId(examMap.get(dbExam.getUserId()).get(dbExam.getId()).get(0).getId());
                }

                exam.setClassId(dbExam.getClassId());
                exam.setCourseId(dbExam.getCourseId());
                exam.setUserId(dbExam.getUserId());
                exam.setExamId(dbExam.getId());
                exam.setUserNumber(userMap.get(dbExam.getUserId()).getUserNumber());
                exam.setUserName(userMap.get(dbExam.getUserId()).getName());
                exam.setName(dbExam.getPaperName());
                exam.setScore(dbExam.getGetScore() == null ? 0 : Integer.valueOf(String.valueOf(dbExam.getGetScore())));
                exam.setIsPassed(new BigDecimal(dbExam.getScore() == null ? 0 : dbExam.getScore())
                        .multiply(new BigDecimal(0.6)).compareTo(new BigDecimal(exam.getScore())) <= 0);
                exam.setExamTime(dbExam.getEndTime() == null ?
                        null : DateUtil.offsetMinute(dbExam.getEndTime(), (-1) * dbExam.getTimeLength()));
                examStatistic.add(exam);
            }
        }

        List<StatisticStudentExam> addData = examStatistic.stream()
                .filter(data -> data.getId() == null).collect(Collectors.toList());
        if (CollectionUtils.isNotEmpty(addData)) {
            studentExamMapperExt.insertBatch(addData);
        }
        List<StatisticStudentExam> updateData = examStatistic.stream()
                .filter(data -> data.getId() != null).collect(Collectors.toList());
        if (CollectionUtils.isNotEmpty(updateData)) {
            studentExamMapperExt.updateBatch(updateData);
        }

    }

    private void insertOrUpdateCourse(List<StatisticStudentChapter> chapters, List<StatisticStudentTask> tasks,
                                      Long courseId, Long classId, List<Long> dbStudentIds,
                                      Map<Long, UserInfoDTO> userMap, List<Long> dbChapterIds,
                                      List<Long> taskIds, Integer allChapterNum) {

        // 获取现有数据库的考勤情况
        List<Statistics> attendances = attendanceService.findAttendanceByUserIds(courseId, classId, dbStudentIds);
        Map<Long, Statistics> attendanceMap = BeanUtil.mapByKey("userId", attendances);

        // 获取现有数据库的问题交流情况
        List<UserProblemAndAnswerDTO> problems = problemService.findProblemAndAnswerInfoByUser(courseId, dbStudentIds);
        Map<Long, UserProblemAndAnswerDTO> problemMap = BeanUtil.mapByKey("userId", problems);

        // 获取现有数据库的随堂提问
        Map<Long, Map<Integer, List<UserQuizStatusDTO>>> quizMap = Maps.newHashMap();
        if (CollectionUtils.isNotEmpty(dbChapterIds)) {
            List<UserQuizStatusDTO> quizs = quizStatisticService.findQuizStatisticByUserIds(dbChapterIds, dbStudentIds);
            quizMap = quizs.stream()
                    .collect(Collectors.groupingBy(UserQuizStatusDTO::getUserId,
                            Collectors.groupingBy(UserQuizStatusDTO::getPerformance)));
        }

        // 获取现有数据库的理论学习时长
        Map<Long, List<TheoryLearnTimeStatistics>> theoryMap = Maps.newHashMap();
        if (CollectionUtils.isNotEmpty(dbChapterIds)) {
            List<TheoryLearnTimeStatistics> theoryVideos = theoryService.findVideoSumInfo(
                    courseId, classId, dbStudentIds, dbChapterIds);
            theoryMap = BeanUtil.aggByKeyToList("userId", theoryVideos);
        }

        // 获取现有数据库的实验学习时长
        Map<Long, List<UserExperimentTimeInfoDTO>> experimentMap = Maps.newHashMap();
        if (CollectionUtils.isNotEmpty(taskIds)) {
            List<UserExperimentTimeInfoDTO> experimentVideos = experimentVideoService.findLearnAndVideoTime(
                    courseId, classId, dbStudentIds, taskIds);
            experimentMap = BeanUtil.aggByKeyToList("userId", experimentVideos);
        }

        // 获取现有数据库的学生课程统计
        List<StatisticStudentCourse> studentCourses = queryStatisticStudentCourse(courseId, classId, null);
        Map<Long, StatisticStudentCourse> courseMap = BeanUtil.mapByKey("userId", studentCourses);

        Map<Long, List<StatisticStudentChapter>> chapterMap = BeanUtil.aggByKeyToList("userId", chapters);
        Map<Long, List<StatisticStudentTask>> taskMap = BeanUtil.aggByKeyToList("userId", tasks);

        // 课程和班级的关联关系
        List<ClassCourse> classCourses = classService.listTeacherClass(null, null, classId, courseId);

        CourseBasicDTO courseInfo = courseService.getCourse(courseId);

        List<StatisticStudentCourse> statistic = Lists.newArrayList();
        for (Long userId : dbStudentIds) {

            StatisticStudentCourse course = new StatisticStudentCourse();

            if (MapUtils.isNotEmpty(courseMap) && courseMap.containsKey(userId)) {
                course.setId(courseMap.get(userId).getId());
            }

            // 基本信息
            course.setCourseId(courseId);
            course.setClassId(classId);
            course.setUserId(userId);
            course.setName(userMap.get(userId).getName());
            course.setUserNumber(userMap.get(userId).getUserNumber());
            course.setCourseName(courseInfo.getName());
            course.setEntryTime(CollectionUtils.isNotEmpty(classCourses) ? classCourses.get(0).getCreateTime() : null);

            // 设置实验学习、视频学习
            List<UserExperimentTimeInfoDTO> experimentTimeData = experimentMap.get(userId);
            long experimentVideoTime = 0;
            long experimentLearnTime = 0;

            if (CollectionUtils.isNotEmpty(experimentTimeData)) {
                experimentVideoTime = experimentTimeData.stream()
                        .map(timeData ->
                                new BigDecimal(timeData.getVideoTime() == null ? "0" : timeData.getVideoTime()))
                        .reduce(BigDecimal::add).get().longValue();
                experimentLearnTime = experimentTimeData.stream()
                        .map(timeData ->
                                new BigDecimal(timeData.getLearnTime() == null ? "0" : timeData.getLearnTime()))
                        .reduce(BigDecimal::add).get().longValue();
            }

            course.setExperimentTime(experimentLearnTime);
            course.setExperimentVideoTime(experimentVideoTime);

            // 设置理论学习、视频学习
            List<TheoryLearnTimeStatistics> theoryData = theoryMap.get(userId);
            long videoTime = 0;
            long learnTime = 0;
            if (CollectionUtils.isNotEmpty(theoryData)) {
                videoTime = theoryData.stream()
                        .map(timeStatistics -> {
                            if (timeStatistics.getVideoTime() == null) {
                                return new BigDecimal(0);
                            }
                            return timeStatistics.getVideoTime();
                        }).reduce(BigDecimal::add).get().longValue();
                learnTime = theoryData.stream()
                        .map(timeStatistics ->
                                new BigDecimal(timeStatistics.getLearnTime() == null ? 0l : timeStatistics.getLearnTime()))
                        .reduce(BigDecimal::add).get().longValue();
            }

            course.setTheoryTime(learnTime);
            course.setTheoryVideoTime(videoTime);

            // 设置实验报告
            List<StatisticStudentTask> studentTasks = taskMap.get(userId);
            studentTasks = studentTasks == null ? Lists.newArrayList() : studentTasks;
            int reportSubmittedNum = studentTasks.stream()
                    .filter(task -> task.getReportSubmitted()).collect(Collectors.toList()).size();
            if (studentTasks.size() < reportSubmittedNum) {
                reportSubmittedNum = studentTasks.size();
            }
            Long reportScore = 0L;
            Long reportCheckedNum = 0L;
            if (CollectionUtils.isNotEmpty(studentTasks)) {
                reportScore = studentTasks.stream()
                        .map(task ->
                                new BigDecimal(task.getReportScore() == null ? 0 : task.getReportScore()))
                        .reduce(BigDecimal::add).get().longValue();
                reportCheckedNum = studentTasks.stream().filter(task -> task.getReportCheck()).count();
            }
            course.setReportSubmittedNum(reportSubmittedNum);
            course.setReportUnsubmittedNum(studentTasks.size() - reportSubmittedNum);
            course.setReportScore(Integer.valueOf(String.valueOf(reportScore)));
            course.setReportCheckedNum(Integer.valueOf(String.valueOf(reportCheckedNum)));

            // 设置随堂练习
            List<StatisticStudentChapter> studentChapters = chapterMap.get(userId);
            studentChapters = studentChapters == null ? Lists.newArrayList() : studentChapters;
            int practiceSubmittedNum = studentChapters.stream()
                    .filter(chapter -> chapter.getPracticeSubmitted()).collect(Collectors.toList()).size();
            if (studentChapters.size() < practiceSubmittedNum) {
                practiceSubmittedNum = studentChapters.size();
            }
            Long practiceScore = 0l;
            Long practiceCheckedNum = 0l;
            if (CollectionUtils.isNotEmpty(studentChapters)) {
                practiceScore = studentChapters.stream()
                        .map(chapter -> new BigDecimal(chapter.getPracticeScore() == null ? 0 : chapter.getPracticeScore()))
                        .reduce(BigDecimal::add).get().longValue();
                practiceCheckedNum = studentChapters.stream().filter(chapter -> chapter.getPracticeCheck()).count();
            }
            course.setPracticeSubmittedNum(practiceSubmittedNum);
            course.setPracticeUnsubmittedNum(studentChapters.size() - practiceSubmittedNum);
            course.setPracticeScore(Integer.valueOf(String.valueOf(practiceScore)));
            course.setPracticeCheckedNum(Integer.valueOf(String.valueOf(practiceCheckedNum)));

            // 设置考勤
            Integer truantNo = attendanceMap.get(userId).getTruantNo();
            truantNo = truantNo == null ? 0 : truantNo;
            course.setTruantNum(allChapterNum < truantNo ? allChapterNum : truantNo);
            course.setAttendanceNum(allChapterNum - course.getTruantNum());

            // 设置问题交流
            Long problemCount = problemMap.get(userId).getProblemCount();
            Long answerCount = problemMap.get(userId).getAnswerCount();
            course.setProblemNum(Integer.valueOf(problemCount == null ? "0" : String.valueOf(problemCount)));
            course.setProblemAnwserNum(Integer.valueOf(answerCount == null ? "0" : String.valueOf(answerCount)));

            // 获取随堂提问
            Map<Integer, List<UserQuizStatusDTO>> quizTypeMap = quizMap.get(userId);
            List<UserQuizStatusDTO> type1List = null;
            List<UserQuizStatusDTO> type2List = null;
            List<UserQuizStatusDTO> quizType1List = null;
            List<UserQuizStatusDTO> quizType2List = null;
            List<UserQuizStatusDTO> quizType3List = null;
            if (MapUtils.isNotEmpty(quizTypeMap)) {
                type1List = quizTypeMap.get(QuizPerformanceEnum.CORRECT.getCode());
                type2List = quizTypeMap.get(QuizPerformanceEnum.INCORRECT.getCode());
                quizType1List = quizTypeMap.get(QuizPerformanceEnum.EXCELLENT.getCode());
                quizType2List = quizTypeMap.get(QuizPerformanceEnum.GOOD.getCode());
                quizType3List = quizTypeMap.get(QuizPerformanceEnum.POOR.getCode());
            }

            // 设置随堂提问
            course.setQuestionAnwserType1Num(CollectionUtils.isEmpty(quizType1List) ? 0 : quizType1List.size());
            course.setQuestionAnwserType2Num(CollectionUtils.isEmpty(quizType2List) ? 0 : quizType2List.size());
            course.setQuestionAnwserType3Num(CollectionUtils.isEmpty(quizType3List) ? 0 : quizType3List.size());
            course.setQuestionAnwserNum(course.getQuestionAnwserType1Num() +
                    course.getQuestionAnwserType2Num() + course.getQuestionAnwserType3Num());

            course.setChoiceAnwserType1Num(CollectionUtils.isEmpty(type1List) ? 0 : type1List.size());
            course.setChoiceAnwserType2Num(CollectionUtils.isEmpty(type2List) ? 0 : type2List.size());
            course.setChoiceAnwserNum(course.getChoiceAnwserType1Num() + course.getChoiceAnwserType2Num());
            statistic.add(course);
        }

        List<StatisticStudentCourse> addData = statistic.stream()
                .filter(studentCourse -> studentCourse.getId() == null).collect(Collectors.toList());
        if (CollectionUtils.isNotEmpty(addData)) {
            studentCourseMapperExt.insertBatch(addData);
        }
        List<StatisticStudentCourse> updateData = statistic.stream()
                .filter(studentCourse -> studentCourse.getId() != null).collect(Collectors.toList());
        if (CollectionUtils.isNotEmpty(updateData)) {
            studentCourseMapperExt.updateBatch(updateData);
        }

    }

    private StduentDTO convert(StatisticStudent statisticStudent, List<StatisticStudentCourse> studentCourses) {

        StduentDTO studentInfo = convert(statisticStudent, null, null, studentCourses);

        // 统计班级信息
        List<Long> classIds = studentCourses.stream()
                .map(StatisticStudentCourse::getClassId).distinct().collect(Collectors.toList());
        List<StatisticClass> statisticClasses = queryStatisticClass(classIds);
        Map<Long, StatisticClass> classMap = BeanUtil.mapByKey("classId", statisticClasses);
        if (MapUtils.isEmpty(classMap)) return studentInfo;

        List<CourseStatisticDTO> courseInfo = studentInfo.getCourseInfo();
        if (CollectionUtils.isNotEmpty(courseInfo)) {
            courseInfo.forEach(statisticDTO -> {
                StatisticClass statisticClass = classMap.get(statisticDTO.getClassId());
                statisticDTO.setClassUpdateTime(statisticClass == null ?
                        null : DateUtil.format(statisticClass.getUpdateTime(), "yyyy.MM.dd HH:mm"));
            });
            studentInfo.setCourseInfo(courseInfo);
        }
        return studentInfo;
    }

    private void deleteStudentTask(Long classId, List<Long> courseIdsForNotDel) {
        StatisticStudentTaskExample example = new StatisticStudentTaskExample();
        StatisticStudentTaskExample.Criteria criteria = example.createCriteria();
        if (classId != null) {
            criteria.andClassIdEqualTo(classId);
        }
        if (CollectionUtils.isNotEmpty(courseIdsForNotDel)) {
            criteria.andCourseIdNotIn(courseIdsForNotDel);
        }
        studentTaskMapper.deleteByExample(example);
    }

    private void deleteStudentTask(Long classId, List<Long> courseIds, List<Long> userIdsForNotDel) {
        StatisticStudentTaskExample example = new StatisticStudentTaskExample();
        StatisticStudentTaskExample.Criteria criteria = example.createCriteria();
        if (classId != null) {
            criteria.andClassIdEqualTo(classId);
        }
        if (CollectionUtils.isNotEmpty(courseIds)) {
            criteria.andCourseIdIn(courseIds);
        }
        if (CollectionUtils.isNotEmpty(userIdsForNotDel)) {
            criteria.andUserIdNotIn(userIdsForNotDel);
        }
        studentTaskMapper.deleteByExample(example);
    }

    private void deleteStudentChapter(Long classId, List<Long> courseIdsForNotDel) {
        StatisticStudentChapterExample example = new StatisticStudentChapterExample();
        StatisticStudentChapterExample.Criteria criteria = example.createCriteria();
        if (classId != null) {
            criteria.andClassIdEqualTo(classId);
        }
        if (CollectionUtils.isNotEmpty(courseIdsForNotDel)) {
            criteria.andCourseIdNotIn(courseIdsForNotDel);
        }
        studentChapterMapper.deleteByExample(example);
    }

    private void deleteStudentChapter(Long classId, List<Long> courseIds, List<Long> userIdsForNotDel) {
        StatisticStudentChapterExample example = new StatisticStudentChapterExample();
        StatisticStudentChapterExample.Criteria criteria = example.createCriteria();
        if (classId != null) {
            criteria.andClassIdEqualTo(classId);
        }
        if (CollectionUtils.isNotEmpty(courseIds)) {
            criteria.andCourseIdIn(courseIds);
        }
        if (CollectionUtils.isNotEmpty(userIdsForNotDel)) {
            criteria.andUserIdNotIn(userIdsForNotDel);
        }
        studentChapterMapper.deleteByExample(example);
    }

    private void deleteStudentCourse(Long classId, List<Long> courseIdsForNotDel) {
        StatisticStudentCourseExample example = new StatisticStudentCourseExample();
        StatisticStudentCourseExample.Criteria criteria = example.createCriteria();
        if (classId != null) {
            criteria.andClassIdEqualTo(classId);
        }
        if (CollectionUtils.isNotEmpty(courseIdsForNotDel)) {
            criteria.andCourseIdNotIn(courseIdsForNotDel);
        }
        studentCourseMapper.deleteByExample(example);
    }

    private void deleteStudentCourse(Long classId, List<Long> courseIds, List<Long> userIdsForNotDel) {
        StatisticStudentCourseExample example = new StatisticStudentCourseExample();
        StatisticStudentCourseExample.Criteria criteria = example.createCriteria();
        if (classId != null) {
            criteria.andClassIdEqualTo(classId);
        }
        if (CollectionUtils.isNotEmpty(courseIds)) {
            criteria.andCourseIdIn(courseIds);
        }
        if (CollectionUtils.isNotEmpty(userIdsForNotDel)) {
            criteria.andUserIdNotIn(userIdsForNotDel);
        }
        studentCourseMapper.deleteByExample(example);
    }

    private void deleteStudentExam(Long classId, List<Long> courseIdsForNotDel) {
        StatisticStudentExamExample example = new StatisticStudentExamExample();
        StatisticStudentExamExample.Criteria criteria = example.createCriteria();
        if (classId != null) {
            criteria.andClassIdEqualTo(classId);
        }
        if (CollectionUtils.isNotEmpty(courseIdsForNotDel)) {
            criteria.andCourseIdNotIn(courseIdsForNotDel);
        }
        studentExamMapper.deleteByExample(example);
    }

    private void deleteStudentExam(Long classId, List<Long> courseIds, List<Long> userIdsForNotDel) {
        StatisticStudentExamExample example = new StatisticStudentExamExample();
        StatisticStudentExamExample.Criteria criteria = example.createCriteria();
        if (classId != null) {
            criteria.andClassIdEqualTo(classId);
        }
        if (CollectionUtils.isNotEmpty(courseIds)) {
            criteria.andCourseIdIn(courseIds);
        }
        if (CollectionUtils.isNotEmpty(userIdsForNotDel)) {
            criteria.andUserIdNotIn(userIdsForNotDel);
        }
        studentExamMapper.deleteByExample(example);
    }

    private void deleteStudentClass(Long classId) {
        StatisticStudentTaskExample example = new StatisticStudentTaskExample();
        example.createCriteria().andClassIdEqualTo(classId);
        studentTaskMapper.deleteByExample(example);
    }

    private StatisticStudent getStatisticStudent(Long userId) {
        StatisticStudentExample example = new StatisticStudentExample();
        example.createCriteria().andUserIdEqualTo(userId);
        List<StatisticStudent> statisticStudents = studentMapper.selectByExample(example);
        if (CollectionUtils.isEmpty(statisticStudents)) return null;
        return statisticStudents.get(0);
    }

    private List<StatisticStudent> queryStatisticStudent(List<Long> userIds) {
        StatisticStudentExample example = new StatisticStudentExample();
        example.createCriteria().andUserIdIn(userIds);
        return studentMapper.selectByExample(example);
    }

    private List<StatisticClass> queryStatisticClass(Long classId) {
        StatisticClassExample example = new StatisticClassExample();
        example.createCriteria().andClassIdEqualTo(classId);
        return statisticClassMapper.selectByExample(example);
    }

    private List<StatisticClass> queryStatisticClass(List<Long> classIds) {
        StatisticClassExample example = new StatisticClassExample();
        example.createCriteria().andClassIdIn(classIds);
        return statisticClassMapper.selectByExample(example);
    }

    private List<StatisticStudentCourse> queryStatisticStudentCourse(Long courseId, Long classId, Long userId) {
        StatisticStudentCourseExample example = new StatisticStudentCourseExample();
        StatisticStudentCourseExample.Criteria criteria = example.createCriteria();
        if (courseId != null) {
            criteria.andCourseIdEqualTo(courseId);
        }
        if (classId != null) {
            criteria.andClassIdEqualTo(classId);
        }
        if (userId != null) {
            criteria.andUserIdEqualTo(userId);
        }
        return studentCourseMapper.selectByExample(example);
    }

    private List<StatisticStudentCourse> queryStatisticStudentCourse(Long courseId, Long classId, List<Long> userIds,
                                                                     String orderByClause) {
        StatisticStudentCourseExample example = new StatisticStudentCourseExample();
        StatisticStudentCourseExample.Criteria criteria = example.createCriteria();
        if (courseId != null) {
            criteria.andCourseIdEqualTo(courseId);
        }
        if (classId != null) {
            criteria.andClassIdEqualTo(classId);
        }
        if (CollectionUtils.isNotEmpty(userIds)) {
            criteria.andUserIdIn(userIds);
        }
        if (StringUtils.isNotBlank(orderByClause)) {
            example.setOrderByClause(orderByClause);
        }
        return studentCourseMapper.selectByExample(example);
    }

    private List<StatisticStudentExam> queryStatisticStudentExam(Long courseId, Long classId) {
        StatisticStudentExamExample example = new StatisticStudentExamExample();
        example.createCriteria().andCourseIdEqualTo(courseId).andClassIdEqualTo(classId);
        example.setOrderByClause("exam_time ASC");
        return studentExamMapper.selectByExample(example);
    }

    private List<StatisticStudentExam> queryStatisticStudentExam(Long courseId, Long classId, Long examId) {
        StatisticStudentExamExample example = new StatisticStudentExamExample();
        example.createCriteria().andCourseIdEqualTo(courseId).andClassIdEqualTo(classId).andExamIdEqualTo(examId);
        example.setOrderByClause("score DESC ,id ASC");
        return studentExamMapper.selectByExample(example);
    }

    private List<StatisticStudentExam> queryStatisticStudentExam(Long classId, List<Long> userIds,
                                                                 String orderByClause) {
        StatisticStudentExamExample example = new StatisticStudentExamExample();
        example.createCriteria().andUserIdIn(userIds).andClassIdEqualTo(classId);
        if (StringUtils.isNotBlank(orderByClause)) {
            example.setOrderByClause(orderByClause);
        }
        return studentExamMapper.selectByExample(example);
    }

    private List<StatisticStudentExam> listStudentExam(Long courseId, Long classId, Long userId, String orderByClause) {
        StatisticStudentExamExample example = new StatisticStudentExamExample();
        example.createCriteria().andCourseIdEqualTo(courseId).andClassIdEqualTo(classId).andUserIdEqualTo(userId);
        if (StringUtils.isNotBlank(orderByClause)) {
            example.setOrderByClause(orderByClause);
        }
        return studentExamMapper.selectByExample(example);
    }

    private List<StatisticStudentTask> queryStatisticStudentTask(Long courseId, Long classId) {
        StatisticStudentTaskExample example = new StatisticStudentTaskExample();
        example.createCriteria().andCourseIdEqualTo(courseId).andClassIdEqualTo(classId);
        return studentTaskMapper.selectByExample(example);
    }

    private List<StatisticStudentTask> queryStatisticStudentTask(Long courseId, Long classId, Long userId) {
        StatisticStudentTaskExample example = new StatisticStudentTaskExample();
        example.createCriteria().andCourseIdEqualTo(courseId).andClassIdEqualTo(classId).andUserIdEqualTo(userId);
        return studentTaskMapper.selectByExample(example);
    }

    private List<StatisticStudentTask> queryStatisticStudentTask(Long classId, List<Long> userIds) {
        StatisticStudentTaskExample example = new StatisticStudentTaskExample();
        example.createCriteria().andClassIdEqualTo(classId).andUserIdIn(userIds);
        return studentTaskMapper.selectByExample(example);
    }

    private List<StatisticStudentChapter> queryStatisticStudentChapter(Long courseId, Long classId, Long userId) {
        StatisticStudentChapterExample example = new StatisticStudentChapterExample();
        StatisticStudentChapterExample.Criteria criteria = example.createCriteria();
        if (courseId != null) {
            criteria.andCourseIdEqualTo(courseId);
        }
        if (classId != null) {
            criteria.andClassIdEqualTo(classId);
        }
        if (userId != null) {
            criteria.andUserIdEqualTo(userId);
        }
        return studentChapterMapper.selectByExample(example);
    }

    private void insertOrUpdateClassStatistic(Class classInfo, Integer peopleNum) {
        List<StatisticClass> statisticClasses = queryStatisticClass(classInfo.getId());
        StatisticClass statisticClass = new StatisticClass();
        statisticClass.setPeopleNum(peopleNum);
        statisticClass.setClassId(classInfo.getId());
        statisticClass.setName(classInfo.getName());
        statisticClass.setGrade(classInfo.getGrade());
        statisticClass.setUpdateTime(new Date());
        if (CollectionUtils.isNotEmpty(statisticClasses)) {
            StatisticClass dbClassStatistic = statisticClasses.get(0);
            statisticClass.setId(dbClassStatistic.getId());
            statisticClass.setCreateTime(dbClassStatistic.getCreateTime());
        }
        if (statisticClass.getId() != null) {
            StatisticClassExample example = new StatisticClassExample();
            example.createCriteria().andIdEqualTo(statisticClass.getId());
            statisticClassMapper.updateByExampleSelective(statisticClass, example);
            return;
        }
        statisticClassMapper.insertSelective(statisticClass);
    }

    private StatisticDTO convert(StatisticStudentCourse statisticSum,
                                 List<StatisticStudentTask> taskStatistic, List<Long> examIds,
                                 List<CourseTeacher> courseTeachers, User teacherUser, StatisticClass statisticClass) {

        long count = 0l;
        long experimentPassedCount = 0l;
        if (CollectionUtils.isNotEmpty(taskStatistic)) {
            count = taskStatistic.stream()
                    .filter(statisticStudentTask -> statisticStudentTask.getExperimentPassed() != null).count();
            experimentPassedCount = taskStatistic.stream()
                    .filter(statisticStudentTask -> statisticStudentTask.getExperimentPassed() != null
                            && statisticStudentTask.getExperimentPassed()).count();
        }

        StatisticDTO statistic = new StatisticDTO();
        if (courseTeachers != null) {
            statistic.setTeacherNum(courseTeachers.size());
        }
        if (teacherUser != null) {
            statistic.setUserName(teacherUser.getName());
            statistic.setIcon(LocalResourcesEnum.SYSTEM_FILE.getMappingUrl(teacherUser.getIcon()));
        }
        if (statisticClass != null) {
            statistic.setClassName(statisticClass.getName());
            statistic.setGrade(statisticClass.getGrade());
            statistic.setStudentNum(statisticClass.getPeopleNum());
        }

        statistic.setExamIds(examIds);

        if (statisticSum != null) {
            // 考勤
            statistic.setAttendanceNum(statisticSum.getAttendanceNum());
            statistic.setTruantNum(statisticSum.getTruantNum());
            Integer sum = statisticSum.getAttendanceNum() + statisticSum.getTruantNum();
            statistic.setAttendanceSum(sum);
            if (!Objects.equals(sum, 0)) {
                statistic.setAttendanceRate(new BigDecimal(statisticSum.getAttendanceNum())
                        .divide(new BigDecimal(sum), 2, BigDecimal.ROUND_HALF_UP).multiply(new BigDecimal(100))
                        .setScale(0, BigDecimal.ROUND_HALF_UP).toString());
            }

            // 时长
            convertTime(statistic, statisticSum);

            // 实验通过
            statistic.setExperimentPassedNum(Integer.valueOf(String.valueOf(experimentPassedCount)));
            statistic.setExperimentUnpassedNum(new BigDecimal(count)
                    .subtract(new BigDecimal(experimentPassedCount)).intValue());
            if (!Objects.equals(count, 0l)) {
                statistic.setExperimentPassedRate(new BigDecimal(experimentPassedCount)
                        .divide(new BigDecimal(count), 2, BigDecimal.ROUND_HALF_UP)
                        .multiply(new BigDecimal(100)).setScale(0, BigDecimal.ROUND_HALF_UP).toString());
            }

            // 实验报告提交
            statistic.setReportSubmittedNum(statisticSum.getReportSubmittedNum());
            statistic.setReportUnsubmittedNum(statisticSum.getReportUnsubmittedNum());
            statistic.setReportScore(statisticSum.getReportScore());
            Integer reportNum = statisticSum.getReportSubmittedNum() + statisticSum.getReportUnsubmittedNum();
            if (!Objects.equals(reportNum, 0)) {
                statistic.setReportSubmittedRate(new BigDecimal(statisticSum.getReportSubmittedNum())
                        .divide(new BigDecimal(reportNum), 2, BigDecimal.ROUND_HALF_UP)
                        .multiply(new BigDecimal(100)).setScale(0, BigDecimal.ROUND_HALF_UP).toString());
            }

            // 随堂练习提交
            statistic.setPracticeSubmittedNum(statisticSum.getPracticeSubmittedNum());
            statistic.setPracticeUnsubmittedNum(statisticSum.getPracticeUnsubmittedNum());
            statistic.setPracticeScore(statisticSum.getPracticeScore());
            Integer practiceNum = statisticSum.getPracticeSubmittedNum() + statisticSum.getPracticeUnsubmittedNum();
            if (!Objects.equals(practiceNum, 0)) {
                statistic.setPracticeSubmittedRate(new BigDecimal(statisticSum.getPracticeSubmittedNum())
                        .divide(new BigDecimal(practiceNum), 2, BigDecimal.ROUND_HALF_UP)
                        .multiply(new BigDecimal(100)).setScale(0, BigDecimal.ROUND_HALF_UP).toString());
            }

            // 问题交流
            statistic.setProblemNum(statisticSum.getProblemNum());
            statistic.setProblemAnwserNum(statisticSum.getProblemAnwserNum());

            // 随堂提问-简答题
            statistic.setQuestionAnwserType1Num(statisticSum.getQuestionAnwserType1Num());
            statistic.setQuestionAnwserType2Num(statisticSum.getQuestionAnwserType2Num());
            statistic.setQuestionAnwserType3Num(statisticSum.getQuestionAnwserType3Num());
            statistic.setQuestionAnwserNum(statisticSum.getQuestionAnwserNum());
            if (!Objects.equals(statisticSum.getQuestionAnwserNum(), 0)) {
                statistic.setQuestionAnwserType1Rate(new BigDecimal(statisticSum.getQuestionAnwserType1Num())
                        .divide(new BigDecimal(statisticSum.getQuestionAnwserNum()), 2, BigDecimal.ROUND_HALF_UP)
                        .multiply(new BigDecimal(100)).setScale(0, BigDecimal.ROUND_HALF_UP).toString());
                statistic.setQuestionAnwserType2Rate(new BigDecimal(statisticSum.getQuestionAnwserType2Num())
                        .divide(new BigDecimal(statisticSum.getQuestionAnwserNum()), 2, BigDecimal.ROUND_HALF_UP)
                        .multiply(new BigDecimal(100)).setScale(0, BigDecimal.ROUND_HALF_UP).toString());
                BigDecimal type3Data = new BigDecimal(100)
                        .subtract(new BigDecimal(Integer.valueOf(statistic.getQuestionAnwserType1Rate())))
                        .subtract(new BigDecimal(Integer.valueOf(statistic.getQuestionAnwserType2Rate())))
                        .setScale(0, BigDecimal.ROUND_HALF_UP);
                if (type3Data.compareTo(new BigDecimal(0)) >= 0) {
                    statistic.setQuestionAnwserType3Rate(type3Data.toString());
                }
            }

            // 随堂提问-判断题
            statistic.setChoiceAnwserType1Num(statisticSum.getChoiceAnwserType1Num());
            statistic.setChoiceAnwserType2Num(statisticSum.getChoiceAnwserType2Num());
            statistic.setChoiceAnwserNum(statisticSum.getChoiceAnwserNum());
            if (!Objects.equals(statisticSum.getChoiceAnwserNum(), 0)) {
                statistic.setChoiceAnwserType1Rate(new BigDecimal(statisticSum.getChoiceAnwserType1Num())
                        .divide(new BigDecimal(statisticSum.getChoiceAnwserNum()), 2, BigDecimal.ROUND_HALF_UP)
                        .multiply(new BigDecimal(100)).setScale(0, BigDecimal.ROUND_HALF_UP).toString());
                BigDecimal type2Data = new BigDecimal(100)
                        .subtract(new BigDecimal(Integer.valueOf(statistic.getChoiceAnwserType1Rate())))
                        .setScale(0, BigDecimal.ROUND_HALF_UP);
                if (type2Data.compareTo(new BigDecimal(0)) >= 0) {
                    statistic.setChoiceAnwserType2Rate(type2Data.toString());
                }
            }

        }

        return statistic;

    }

    private <T extends TimeDTO> void convertTime(T statisticT, StatisticStudentCourse studentCourse) {
        statisticT.setExperimentTime(convertTime(studentCourse.getExperimentTime()));
        statisticT.setExperimentVideoTime(convertTime(studentCourse.getExperimentVideoTime()));
        statisticT.setTheoryTime(convertTime(studentCourse.getTheoryTime()));
        statisticT.setTheoryVideoTime(convertTime(studentCourse.getTheoryVideoTime()));
    }

    public static String convertTime(Long time) {
        time = time == null ? 0l : time;
        String chineseExperimentTime = DateUtil.formatBetween(time * 1000, BetweenFormater.Level.SECOND);
        if (chineseExperimentTime.contains("天")) {
            if (chineseExperimentTime.lastIndexOf("时") == -1) {
                return chineseExperimentTime.substring(0, chineseExperimentTime.lastIndexOf("天") + 1);
            } else {
                return chineseExperimentTime.substring(0, chineseExperimentTime.lastIndexOf("时") + 1);
            }
        } else if (chineseExperimentTime.contains("时")) {
            if (chineseExperimentTime.lastIndexOf("分") == -1) {
                return chineseExperimentTime.substring(0, chineseExperimentTime.lastIndexOf("时") + 1);
            } else {
                return chineseExperimentTime.substring(0, chineseExperimentTime.lastIndexOf("分") + 1);
            }
        }
        return chineseExperimentTime;
    }

    private void deleteStatistic(Long classId, List<Long> courseIdsForNotDel) {
        deleteStudentTask(classId, courseIdsForNotDel);
        deleteStudentChapter(classId, courseIdsForNotDel);
        deleteStudentExam(classId, courseIdsForNotDel);
        deleteStudentCourse(classId, courseIdsForNotDel);
    }

    private void deleteStatistic(Long classId, List<Long> courseIds, List<Long> userIdsForNotDel) {
        deleteStudentTask(classId, courseIds, userIdsForNotDel);
        deleteStudentChapter(classId, courseIds, userIdsForNotDel);
        deleteStudentExam(classId, courseIds, userIdsForNotDel);
        deleteStudentCourse(classId, courseIds, userIdsForNotDel);
    }

    private List<UserInfoDTO> queryUserInfo(Long classId) {
        return userService.queryClassUser(null, Lists.newArrayList(classId),
                Lists.newArrayList(UserStatusEnum.NOT_DELETE.getCode(), UserStatusEnum.ACCOUNT_CANCELLATION.getCode()),
                false);
    }

    private Map<Long, UserInfoDTO> convertUserMap(Map<Long, UserInfoDTO> map, List<Long> userIds) {
        if (CollectionUtils.isEmpty(userIds)) return map;
        map = map == null ? Maps.newHashMap() : map;
        List<Long> dbUserIds = Lists.newArrayList(map.keySet());
        List<Long> queryUserIds = userIds.stream()
                .filter(userId -> !dbUserIds.contains(userId)).collect(Collectors.toList());
        if (CollectionUtils.isNotEmpty(queryUserIds)) {
            try {
                List<UserInfoDTO> userInfos = userService.queryUserInfos(queryUserIds, null);
                if (CollectionUtils.isNotEmpty(userInfos)) {
                    Map<Long, UserInfoDTO> userInfoMap = BeanUtil.mapByKey("id", userInfos);
                    map.putAll(userInfoMap);
                }
            } catch (Exception e) {

            }
        }

        return map;
    }

    private void deleteStudentExam(Long courseId, Long classId, List<Long> dbExamIds) {
        StatisticStudentExamExample example = new StatisticStudentExamExample();
        StatisticStudentExamExample.Criteria criteria = example.createCriteria();
        if (courseId != null) {
            criteria.andCourseIdEqualTo(courseId);
        }
        if (classId != null) {
            criteria.andClassIdEqualTo(classId);
        }
        if (CollectionUtils.isNotEmpty(dbExamIds)) {
            criteria.andExamIdNotIn(dbExamIds);
        }
        studentExamMapper.deleteByExample(example);
    }

    private List<Long> convertChapter(List<ChapterBasicInfoDTO> dbChapters) {
        return ChapterEnum.CHAPTER.getConvertData(dbChapters).stream()
                .filter(chapter -> ChapterTypeEnum.isContainsTheory(chapter.getType()))
                .map(ChapterBasicInfoDTO::getId).collect(Collectors.toList());
    }

    private List<Long> convertTask(List<ChapterBasicInfoDTO> dbChapters) {
        return ChapterEnum.TASK.getConvertData(dbChapters).stream()
                .filter(chapterInfo -> chapterInfo.getParentId() != null && chapterInfo.getParentId() > 0)
                .map(ChapterBasicInfoDTO::getId)
                .collect(Collectors.toList());
    }

    private void deleteStudentTask(Long courseId, Long classId, List<Long> dbTaskIds) {
        StatisticStudentTaskExample example = new StatisticStudentTaskExample();
        StatisticStudentTaskExample.Criteria criteria = example.createCriteria();
        if (courseId != null) {
            criteria.andCourseIdEqualTo(courseId);
        }
        if (classId != null) {
            criteria.andClassIdEqualTo(classId);
        }
        if (CollectionUtils.isNotEmpty(dbTaskIds)) {
            criteria.andTaskIdNotIn(dbTaskIds);
        }
        studentTaskMapper.deleteByExample(example);
    }

    private void deleteStudentChapter(Long courseId, Long classId, List<Long> dbChapterIds) {
        StatisticStudentChapterExample example = new StatisticStudentChapterExample();
        StatisticStudentChapterExample.Criteria criteria = example.createCriteria();
        if (courseId != null) {
            criteria.andCourseIdEqualTo(courseId);
        }
        if (classId != null) {
            criteria.andClassIdEqualTo(classId);
        }
        if (CollectionUtils.isNotEmpty(dbChapterIds)) {
            criteria.andChapterIdNotIn(dbChapterIds);
        }
        studentChapterMapper.deleteByExample(example);
    }

    private StudentExamDTO convertStudentExam(List<StatisticStudentExam> exams) {
        int peopleNum = exams.size();
        List<StudentExamInfoDTO> studentExams = Lists.newArrayList();
        BigDecimal score = new BigDecimal(0);
        BigDecimal passedNum = new BigDecimal(0);

        for (StatisticStudentExam studentExam : exams) {
            if (studentExam.getIsPassed()) {
                passedNum = passedNum.add(new BigDecimal(1));
            }
            Integer dbScore = studentExam.getScore();
            dbScore = dbScore == null ? 0 : dbScore;
            score = score.add(new BigDecimal(dbScore));
            studentExams.add(
                    StudentExamInfoDTO.builder().userName(studentExam.getUserName()).score(dbScore).build()
            );
        }

        StudentExamDTO studentExam = new StudentExamDTO();
        studentExam.setExamName(exams.get(0).getName());
        studentExam.setInfo(studentExams);
        if (!Objects.equals(peopleNum, 0)) {
            studentExam.setPassedRate(passedNum
                    .divide(new BigDecimal(Integer.valueOf(peopleNum)), 2, BigDecimal.ROUND_HALF_UP)
                    .multiply(new BigDecimal(100)).setScale(0, BigDecimal.ROUND_HALF_UP).toString());
            studentExam.setAverageScore(score
                    .divide(new BigDecimal(Integer.valueOf(peopleNum)), 1, BigDecimal.ROUND_HALF_UP).toString());
        }

        return studentExam;

    }

    private List<StatisticInfoDTO> convertStatisticInfoDTO(List<StatisticStudentCourse> dbStatisticList) {

        List<StatisticInfoDTO> infoList = Lists.newArrayList();
        if (CollectionUtils.isEmpty(dbStatisticList)) return infoList;

        dbStatisticList.forEach(dbStatistic -> {
            StatisticInfoDTO info = new StatisticInfoDTO();
            info.setId(dbStatistic.getId());
            info.setUserNumber(dbStatistic.getUserNumber());
            info.setName(dbStatistic.getName());
            info.setTruantNum(dbStatistic.getTruantNum());
            info.setProblemNum(dbStatistic.getProblemNum());
            info.setProblemAnwserNum(dbStatistic.getProblemAnwserNum());
            info.setQuestionAnwserNum(dbStatistic.getQuestionAnwserNum());
            info.setQuestionAnwserType1Num(dbStatistic.getQuestionAnwserType1Num());
            info.setQuestionAnwserType2Num(dbStatistic.getQuestionAnwserType2Num());
            info.setQuestionAnwserType3Num(dbStatistic.getQuestionAnwserType3Num());
            info.setChoiceAnwserNum(dbStatistic.getChoiceAnwserNum());
            info.setChoiceAnwserType1Num(dbStatistic.getChoiceAnwserType1Num());
            info.setChoiceAnwserType2Num(dbStatistic.getChoiceAnwserType2Num());
            info.setManifestScore(dbStatistic.getManifestScore());
            convertTime(info, dbStatistic);
            infoList.add(info);
        });
        return infoList;
    }

    private StatisticDTO convertStatisticDTO(StatisticStudentCourse studentStatistic,
                                             List<StatisticStudentTask> taskStatistic,
                                             List<StatisticStudentExam> studentExams) {
        StatisticDTO statisticDTO = convert(studentStatistic, taskStatistic, null, null,
                null, null);
        List<Integer> scores = Lists.newArrayList();
        if (CollectionUtils.isNotEmpty(studentExams)) {
            scores = studentExams.stream().map(StatisticStudentExam::getScore).collect(Collectors.toList());
        }
        statisticDTO.setExamScores(scores);
        return statisticDTO;
    }

    private StduentDTO convert(StatisticStudent statisticStudent, StatisticClass statisticClass,
                               List<Long> classStudentIds, List<StatisticStudentCourse> studentCourses) {
        StduentDTO stduentDTO = new StduentDTO();
        if (statisticClass != null) {
            stduentDTO.setClassName(statisticClass.getName());
            stduentDTO.setGrade(statisticClass.getGrade());
        }
        if (statisticStudent != null) {
            stduentDTO.setUserNumber(statisticStudent.getUserNumber());
            stduentDTO.setName(statisticStudent.getName());
            stduentDTO.setIconUrl(LocalResourcesEnum.SYSTEM_FILE.getMappingUrl(statisticStudent.getIcon()));
            stduentDTO.setCourseNum(statisticStudent.getCourseNum());
            stduentDTO.setLoginNum(statisticStudent.getLoginNum());
            stduentDTO.setRegTime(DateUtil.format(statisticStudent.getRegTime(), "yyyy/MM/dd"));
            stduentDTO.setLearnTime(convertTime(Long.valueOf(statisticStudent.getLearnTime())));
        }
        if (CollectionUtils.isNotEmpty(classStudentIds)) {
            int index = classStudentIds.indexOf(statisticStudent.getUserId());
            if (index > 0) {
                stduentDTO.setPreviousUserId(classStudentIds.get(index - 1));
            }
            if ((index + 1) < classStudentIds.size()) {
                stduentDTO.setNextUserId(classStudentIds.get(index + 1));
            }
        }

        if (CollectionUtils.isNotEmpty(studentCourses)) {
            List<CourseStatisticDTO> courseInfo = Lists.newArrayList();
            studentCourses.forEach(studentCourse -> {
                CourseStatisticDTO statisticDTO = new CourseStatisticDTO();
                statisticDTO.setId(studentCourse.getCourseId());
                statisticDTO.setName(studentCourse.getCourseName());
                statisticDTO.setClassId(studentCourse.getClassId());
                statisticDTO.setEntryTime(DateUtil.format(studentCourse.getEntryTime(), "yyyy/MM/dd"));
                courseInfo.add(statisticDTO);
            });
            stduentDTO.setCourseInfo(courseInfo);
        }

        return stduentDTO;
    }

    private List<String> convertSheetNames(List<StatisticStudent> userInfos) {
        Map<String, Long> nameMap = userInfos.stream()
                .collect(Collectors.groupingBy(StatisticStudent::getName, Collectors.counting()));

        List<String> sheetNames = Lists.newArrayList();
        for (StatisticStudent userInfo : userInfos) {
            String sheetName = userInfo.getName();
            if (nameMap.get(userInfo.getName()) > 1) {
                sheetName = StringUtils.join(sheetName, "-", userInfo.getUserNumber());
            }
            sheetNames.add(sheetName);
        }
        return sheetNames;
    }

    private List<List<String>> convertBasicInfo(List<StatisticStudent> statisticStudents, String className) {
        List<List<String>> basicInfos = Lists.newArrayList();
        for (StatisticStudent userInfo : statisticStudents) {
            List<String> data = Lists.newArrayList();
            data.add(className);
            data.add(userInfo.getName());
            data.add(userInfo.getUserNumber());
            data.add(userInfo.getCourseNum() == null ? "0" : String.valueOf(userInfo.getCourseNum()));
            data.add(userInfo.getRegTime() == null ? "" : DateUtil.format(userInfo.getRegTime(), "yyyy/MM/dd"));
            data.add(userInfo.getLoginNum() == null ? "0" : String.valueOf(userInfo.getLoginNum()));
            data.add(convertTime(Long.valueOf(userInfo.getLearnTime())));
            basicInfos.add(data);
        }
        return basicInfos;
    }

    private Table<Integer, Integer, List<Object>> convertTable(Integer examNum) {
        Table<Integer, Integer, List<Object>> table = HashBasedTable.create();
        table.put(0, 0, Lists.newArrayList("序号", true));
        table.put(1, 0, Lists.newArrayList("序号", true));

        table.put(0, 1, Lists.newArrayList("课程名称", true));
        table.put(1, 1, Lists.newArrayList("课程名称", true));

        table.put(1, 2, Lists.newArrayList("加入时间", true));
        table.put(0, 2, Lists.newArrayList("加入时间", true));

        table.put(0, 3, Lists.newArrayList("实验视频总时长", true));
        table.put(1, 3, Lists.newArrayList("实验视频总时长", true));

        table.put(1, 4, Lists.newArrayList("实验学习总时长", true));
        table.put(0, 4, Lists.newArrayList("实验学习总时长", true));

        table.put(0, 5, Lists.newArrayList("实验通过率", true));
        table.put(1, 5, Lists.newArrayList("实验通过率", true));

        table.put(0, 7, Lists.newArrayList("实验报告", true));
        table.put(0, 6, Lists.newArrayList("实验报告", true));

        table.put(1, 6, Lists.newArrayList("提交次数"));
        table.put(1, 7, Lists.newArrayList("累计分值"));

        table.put(1, 8, Lists.newArrayList("理论视频总时长", true));
        table.put(0, 8, Lists.newArrayList("理论视频总时长", true));

        table.put(0, 9, Lists.newArrayList("理论学习总时长", true));
        table.put(1, 9, Lists.newArrayList("理论学习总时长", true));

        table.put(0, 11, Lists.newArrayList("随堂练习", true));
        table.put(0, 10, Lists.newArrayList("随堂练习", true));

        table.put(1, 10, Lists.newArrayList("提交次数"));
        table.put(1, 11, Lists.newArrayList("累计分值"));

        table.put(0, 12, Lists.newArrayList("随堂提问问答回答总次数", true));
        table.put(1, 12, Lists.newArrayList("随堂提问问答回答总次数", true));

        table.put(0, 13, Lists.newArrayList("问答题表现", true));
        table.put(0, 14, Lists.newArrayList("问答题表现", true));
        table.put(0, 15, Lists.newArrayList("问答题表现", true));

        table.put(1, 13, Lists.newArrayList("优秀"));
        table.put(1, 14, Lists.newArrayList("良好"));
        table.put(1, 15, Lists.newArrayList("差劲"));

        table.put(0, 16, Lists.newArrayList("随堂提问判断回答总次数", true));
        table.put(1, 16, Lists.newArrayList("随堂提问判断回答总次数", true));

        table.put(0, 17, Lists.newArrayList("判断题表现", true));
        table.put(0, 18, Lists.newArrayList("判断题表现", true));

        table.put(1, 17, Lists.newArrayList("正确"));
        table.put(1, 18, Lists.newArrayList("错误"));

        table.put(1, 19, Lists.newArrayList("旷课次数", true));
        table.put(0, 19, Lists.newArrayList("旷课次数", true));

        table.put(0, 20, Lists.newArrayList("问题交流", true));
        table.put(0, 21, Lists.newArrayList("问题交流", true));

        table.put(1, 20, Lists.newArrayList("提问数"));
        table.put(1, 21, Lists.newArrayList("回答数"));

        int columnKey = 22;
        for (Integer i = 0; i < examNum; i++) {
            table.put(0, columnKey, Lists.newArrayList("考试成绩", true));
            table.put(1, columnKey, Lists.newArrayList("第" + (i + 1) + "次"));
            columnKey = columnKey + 1;
        }
        return table;
    }

    private List<Table<Integer, Integer, List<Object>>> convertTable(
            List<StatisticStudent> statisticStudents, Map<Long, List<StatisticStudentExam>> userExamMap) {
        List<Table<Integer, Integer, List<Object>>> tables = Lists.newArrayList();
        for (StatisticStudent userInfo : statisticStudents) {
            Integer examNum = 0;
            if (MapUtils.isNotEmpty(userExamMap)) {
                List<StatisticStudentExam> studentExamList = userExamMap.get(userInfo.getUserId());
                if (CollectionUtils.isNotEmpty(studentExamList)) {
                    Map<Long, Long> courseExamNum = studentExamList.stream()
                            .collect(Collectors.groupingBy(StatisticStudentExam::getCourseId, Collectors.counting()));
                    examNum = courseExamNum.values().stream().reduce(Long::max).get().intValue();
                }
            }
            tables.add(convertTable(examNum));
        }
        return tables;
    }

    private List<List<List<String>>> convertList(List<StatisticStudent> statisticStudents,
                                                 Map<Long, List<StatisticStudentCourse>> userCourseMap,
                                                 Map<Long, List<StatisticStudentExam>> userExamMap,
                                                 Map<Long, List<StatisticStudentTask>> taskMap) {
        List<List<List<String>>> listData = Lists.newArrayList();
        for (StatisticStudent userInfo : statisticStudents) {
            // 获取课程
            List<StatisticStudentCourse> courseList = userCourseMap.get(userInfo.getUserId());
            if (CollectionUtils.isEmpty(courseList)) continue;

            // 根据课程id转换用户某课程的考试信息
            List<StatisticStudentExam> exams = userExamMap.get(userInfo.getUserId());
            Map<Long, List<StatisticStudentExam>> courseExamMap = Maps.newHashMap();
            Integer maxExamNum = 0;
            if (CollectionUtils.isNotEmpty(exams)) {
                courseExamMap = BeanUtil.aggByKeyToList("courseId", exams);
                Map<Long, Long> courseExamNum = exams.stream()
                        .collect(Collectors.groupingBy(StatisticStudentExam::getCourseId, Collectors.counting()));
                maxExamNum = courseExamNum.values().stream().reduce(Long::max).get().intValue();
            }

            List<StatisticStudentTask> studentTasks = taskMap.get(userInfo.getUserId());
            Map<Long, List<StatisticStudentTask>> userTaskMap = Maps.newHashMap();
            if (CollectionUtils.isNotEmpty(studentTasks)) {
                userTaskMap = BeanUtil.aggByKeyToList("courseId", studentTasks);
            }

            // 组装数据
            List<List<String>> list = Lists.newArrayList();
            for (int i = 0; i < courseList.size(); i++) {

                StatisticStudentCourse course = courseList.get(i);
                List<StatisticStudentExam> examData = courseExamMap.get(course.getCourseId());
                List<StatisticStudentTask> taskStatistic = userTaskMap.get(course.getCourseId());

                // 统计
                StatisticDTO statistic = convertStatisticDTO(course, taskStatistic, examData);

                // 配置结果集
                List<String> result = Lists.newArrayList((i + 1) + "",
                        course.getCourseName() == null ? "" : course.getCourseName(),
                        course.getEntryTime() == null ?
                                "" : DateUtil.format(course.getEntryTime(), "yyyy/MM/dd"),
                        statistic.getExperimentVideoTime(), statistic.getExperimentTime(),
                        statistic.getExperimentPassedRate() + "%",
                        statistic.getReportSubmittedNum() == null ?
                                "0" : String.valueOf(statistic.getReportSubmittedNum()),
                        statistic.getReportScore() == null ? "0" : String.valueOf(statistic.getReportScore()),
                        statistic.getTheoryVideoTime(), statistic.getTheoryTime(),
                        statistic.getPracticeSubmittedNum() == null ?
                                "0" : String.valueOf(statistic.getPracticeSubmittedNum()),
                        statistic.getPracticeScore() == null ?
                                "0" : String.valueOf(statistic.getPracticeScore()),
                        statistic.getQuestionAnwserNum() == null ?
                                "0" : String.valueOf(statistic.getQuestionAnwserNum()),
                        statistic.getQuestionAnwserType1Num() == null ?
                                "0" : String.valueOf(statistic.getQuestionAnwserType1Num()),
                        statistic.getQuestionAnwserType2Num() == null ?
                                "0" : String.valueOf(statistic.getQuestionAnwserType2Num()),
                        statistic.getQuestionAnwserType3Num() == null ?
                                "0" : String.valueOf(statistic.getQuestionAnwserType3Num()),
                        statistic.getChoiceAnwserNum() == null ?
                                "0" : String.valueOf(statistic.getChoiceAnwserNum()),
                        statistic.getChoiceAnwserType1Num() == null ?
                                "0" : String.valueOf(statistic.getChoiceAnwserType1Num()),
                        statistic.getChoiceAnwserType2Num() == null ?
                                "0" : String.valueOf(statistic.getChoiceAnwserType2Num()),
                        statistic.getTruantNum() == null ?
                                "0" : String.valueOf(statistic.getTruantNum()),
                        statistic.getProblemNum() == null ?
                                "0" : String.valueOf(statistic.getProblemNum()),
                        statistic.getProblemAnwserNum() == null ?
                                "0" : String.valueOf(statistic.getProblemAnwserNum())
                );
                List<Integer> scores = statistic.getExamScores() == null ?
                        Lists.newArrayList() : statistic.getExamScores();
                result.addAll(statistic.getExamScores().stream().map(String::valueOf).collect(Collectors.toList()));
                if (maxExamNum > scores.size()) {
                    int num = maxExamNum - scores.size();
                    for (int z = 0; z < num; z++) {
                        result.add("");
                    }
                }

                list.add(result);
            }
            listData.add(list);
        }
        return listData;
    }

    private List<String> convertBasicInfo(StatisticDTO statisticDTO, String courseName,
                                          List<StatisticStudentExam> studentExams) {
        List<String> basicInfos = Lists.newArrayList();
        basicInfos.add(courseName == null ? "" : courseName);
        basicInfos.add(statisticDTO == null ? "" : statisticDTO.getClassName());
        basicInfos.add(statisticDTO == null ? "" : statisticDTO.getUserName());
        basicInfos.add(String.valueOf(statisticDTO == null ? 0 : statisticDTO.getStudentNum()));
        basicInfos.add(String.valueOf(statisticDTO == null ? 0 : statisticDTO.getTeacherNum()));
        basicInfos.add(statisticDTO == null ? "0%" : statisticDTO.getAttendanceRate() + "%");
        basicInfos.add(String.valueOf(statisticDTO == null ? 0 : statisticDTO.getTruantNum()));
        basicInfos.add(String.valueOf(statisticDTO == null ? 0 : statisticDTO.getExamIds().size()));

        String str = "";
        Map<Long, List<StatisticStudentExam>> examMap = BeanUtil.aggByKeyToList("examId", studentExams);
        for (int i = 0; i < statisticDTO.getExamIds().size(); i++) {
            String average = "0";
            Long examId = statisticDTO.getExamIds().get(i);
            List<StatisticStudentExam> examList = examMap.get(examId);
            if (CollectionUtils.isNotEmpty(examList)) {
                double aver = examList.stream().mapToLong(value -> {
                    if (value.getScore() == null) {
                        return 0;
                    }
                    return value.getScore();
                }).average().getAsDouble();
                average = new BigDecimal(aver).setScale(1, BigDecimal.ROUND_HALF_UP).toString();
            }
            str = StringUtils.join(str, "第" + (i + 1) + "次:" + average + "分  ");
        }
        basicInfos.add(str);

        basicInfos.add(statisticDTO == null ? "0秒" : statisticDTO.getExperimentVideoTime());
        basicInfos.add(statisticDTO == null ? "0秒" : statisticDTO.getExperimentTime());
        basicInfos.add(statisticDTO == null ? "0%" : statisticDTO.getExperimentPassedRate() + "%");
        basicInfos.add(statisticDTO == null ? "0%" : statisticDTO.getReportSubmittedRate() + "%");
        basicInfos.add(statisticDTO == null ? "0秒" : statisticDTO.getTheoryVideoTime());
        basicInfos.add(statisticDTO == null ? "0秒" : statisticDTO.getTheoryTime());
        basicInfos.add(statisticDTO == null ? "0%" : statisticDTO.getPracticeSubmittedRate() + "%");
        basicInfos.add(statisticDTO == null ? "0" : String.valueOf(statisticDTO.getQuestionAnwserNum()));
        basicInfos.add(String.format("优:%s 良:%s 差:%s",
                statisticDTO == null ? "0%" : statisticDTO.getQuestionAnwserType1Rate() + "%",
                statisticDTO == null ? "0%" : statisticDTO.getQuestionAnwserType2Rate() + "%",
                statisticDTO == null ? "0%" : statisticDTO.getQuestionAnwserType3Rate() + "%"));
        basicInfos.add(statisticDTO == null ? "0" : String.valueOf(statisticDTO.getChoiceAnwserNum()));
        basicInfos.add(String.format("正确:%s 错误:%s",
                statisticDTO == null ? "0%" : statisticDTO.getChoiceAnwserType1Rate() + "%",
                statisticDTO == null ? "0%" : statisticDTO.getChoiceAnwserType2Rate() + "%"));
        basicInfos.add(statisticDTO == null ? "0" : String.valueOf(statisticDTO.getProblemNum()));
        basicInfos.add(statisticDTO == null ? "0" : String.valueOf(statisticDTO.getProblemAnwserNum()));
        return basicInfos;
    }

    private Table<Integer, Integer, List<Object>> convertTitle(Integer examNum) {

        Table<Integer, Integer, List<Object>> table = HashBasedTable.create();
        table.put(0, 0, Lists.newArrayList("序号", true));
        table.put(1, 0, Lists.newArrayList("序号", true));

        table.put(0, 1, Lists.newArrayList("学生姓名", true));
        table.put(1, 1, Lists.newArrayList("学生姓名", true));

        table.put(1, 2, Lists.newArrayList("学生学号", true));
        table.put(0, 2, Lists.newArrayList("学生学号", true));

        table.put(0, 3, Lists.newArrayList("实验视频总时长", true));
        table.put(1, 3, Lists.newArrayList("实验视频总时长", true));

        table.put(1, 4, Lists.newArrayList("实验学习总时长", true));
        table.put(0, 4, Lists.newArrayList("实验学习总时长", true));

        table.put(0, 5, Lists.newArrayList("实验通过率", true));
        table.put(1, 5, Lists.newArrayList("实验通过率", true));

        table.put(0, 7, Lists.newArrayList("实验报告", true));
        table.put(0, 6, Lists.newArrayList("实验报告", true));

        table.put(1, 6, Lists.newArrayList("提交次数"));
        table.put(1, 7, Lists.newArrayList("平均分值"));

        table.put(1, 8, Lists.newArrayList("理论视频总时长", true));
        table.put(0, 8, Lists.newArrayList("理论视频总时长", true));

        table.put(0, 9, Lists.newArrayList("理论学习总时长", true));
        table.put(1, 9, Lists.newArrayList("理论学习总时长", true));

        table.put(0, 11, Lists.newArrayList("随堂练习", true));
        table.put(0, 10, Lists.newArrayList("随堂练习", true));

        table.put(1, 10, Lists.newArrayList("提交次数"));
        table.put(1, 11, Lists.newArrayList("平均分值"));

        table.put(0, 12, Lists.newArrayList("随堂提问问答回答总次数", true));
        table.put(1, 12, Lists.newArrayList("随堂提问问答回答总次数", true));

        table.put(0, 13, Lists.newArrayList("问答题表现", true));
        table.put(0, 14, Lists.newArrayList("问答题表现", true));
        table.put(0, 15, Lists.newArrayList("问答题表现", true));

        table.put(1, 13, Lists.newArrayList("优秀"));
        table.put(1, 14, Lists.newArrayList("良好"));
        table.put(1, 15, Lists.newArrayList("差劲"));

        table.put(0, 16, Lists.newArrayList("随堂提问判断回答总次数", true));
        table.put(1, 16, Lists.newArrayList("随堂提问判断回答总次数", true));

        table.put(0, 17, Lists.newArrayList("判断题表现", true));
        table.put(0, 18, Lists.newArrayList("判断题表现", true));

        table.put(1, 17, Lists.newArrayList("正确"));
        table.put(1, 18, Lists.newArrayList("错误"));

        table.put(1, 19, Lists.newArrayList("旷课次数", true));
        table.put(0, 19, Lists.newArrayList("旷课次数", true));

        table.put(0, 20, Lists.newArrayList("问题交流", true));
        table.put(0, 21, Lists.newArrayList("问题交流", true));

        table.put(1, 20, Lists.newArrayList("提问数"));
        table.put(1, 21, Lists.newArrayList("回答数"));

        int columnKey = 22;
        for (Integer i = 0; i < examNum; i++) {
            table.put(0, columnKey, Lists.newArrayList("考试成绩", true));
            table.put(1, columnKey, Lists.newArrayList("第" + (i + 1) + "次"));
            columnKey = columnKey + 1;
        }
        return table;
    }

    private List<List<String>> convertList(List<StatisticStudentCourse> studentCourses,
                                           Map<Long, List<StatisticStudentExam>> examMap,
                                           Map<Long, List<StatisticStudentTask>> taskMap, Integer examNum) {
        List<List<String>> listData = Lists.newArrayList();
        if (CollectionUtils.isEmpty(studentCourses)) return listData;

        if (MapUtils.isEmpty(examMap)) {
            examMap = Maps.newHashMap();
        }
        if (MapUtils.isEmpty(taskMap)) {
            taskMap = Maps.newHashMap();
        }

        for (int i = 0; i < studentCourses.size(); i++) {
            StatisticStudentCourse studentCourse = studentCourses.get(i);

            StatisticInfoDTO info = new StatisticInfoDTO();
            convertTime(info, studentCourse);

            String passedRate = "0";
            List<StatisticStudentTask> studentTasks = taskMap.get(studentCourse.getUserId());
            if (CollectionUtils.isNotEmpty(studentTasks)) {
                long count = studentTasks.stream()
                        .filter(statisticStudentTask -> statisticStudentTask.getExperimentPassed() != null).count();
                long experimentPassedCount = studentTasks.stream()
                        .filter(statisticStudentTask -> statisticStudentTask.getExperimentPassed() != null
                                && statisticStudentTask.getExperimentPassed()).count();
                if (count > 0) {
                    passedRate = new BigDecimal(experimentPassedCount)
                            .divide(new BigDecimal(count), 2, BigDecimal.ROUND_HALF_UP)
                            .multiply(new BigDecimal(100)).setScale(0, BigDecimal.ROUND_HALF_UP).toString();
                }
            }

            String reportAvg = "0";
            if(studentCourse.getReportScore() != null
                    && studentCourse.getReportScore() != 0
                    && studentCourse.getReportCheckedNum() != null
                    && studentCourse.getReportCheckedNum() != 0){

                reportAvg = NumberUtil.round(NumberUtil.div(studentCourse.getReportScore(),
                        studentCourse.getReportCheckedNum()), 2).toPlainString();

            }

            // 随堂练习平均分数
            String practiceAvg = "0";
            if(studentCourse.getPracticeScore() != null
                    && studentCourse.getPracticeScore() != 0
                    && studentCourse.getPracticeCheckedNum() != null
                    && studentCourse.getPracticeCheckedNum() != 0){

                practiceAvg =  NumberUtil.round(NumberUtil.div(studentCourse.getPracticeScore(),
                        studentCourse.getPracticeCheckedNum()), 2).toPlainString();
            }

            List<String> data = Lists.newArrayList((i + 1) + "",
                    studentCourse.getName() == null ? "" : studentCourse.getName(),
                    studentCourse.getUserNumber() == null ? "" : studentCourse.getUserNumber(),
                    info.getExperimentVideoTime() == null ? "0秒" : info.getExperimentVideoTime(),
                    info.getExperimentVideoTime() == null ? "0秒" : info.getExperimentTime(),
                    passedRate + "%",
                    studentCourse.getReportSubmittedNum() == null ?
                            "0" : String.valueOf(studentCourse.getReportSubmittedNum()),
                    // 实验报告平均分值
                    reportAvg,

                    info.getTheoryVideoTime() == null ? "0秒" : info.getTheoryVideoTime(),
                    info.getTheoryTime() == null ? "0秒" : info.getTheoryTime(),
                    studentCourse.getPracticeSubmittedNum() == null ?
                            "0" : String.valueOf(studentCourse.getPracticeSubmittedNum()),
                    // 随堂练习平均分值
                    practiceAvg,

                    studentCourse.getQuestionAnwserNum() == null ?
                            "0" : String.valueOf(studentCourse.getQuestionAnwserNum()),
                    studentCourse.getQuestionAnwserType1Num() == null ?
                            "0" : String.valueOf(studentCourse.getQuestionAnwserType1Num()),
                    studentCourse.getQuestionAnwserType2Num() == null ?
                            "0" : String.valueOf(studentCourse.getQuestionAnwserType2Num()),
                    studentCourse.getQuestionAnwserType3Num() == null ?
                            "0" : String.valueOf(studentCourse.getQuestionAnwserType3Num()),
                    studentCourse.getChoiceAnwserNum() == null ?
                            "0" : String.valueOf(studentCourse.getChoiceAnwserNum()),
                    studentCourse.getChoiceAnwserType1Num() == null ?
                            "0" : String.valueOf(studentCourse.getChoiceAnwserType1Num()),
                    studentCourse.getChoiceAnwserType2Num() == null ?
                            "0" : String.valueOf(studentCourse.getChoiceAnwserType2Num()),
                    studentCourse.getTruantNum() == null ? "0" : String.valueOf(studentCourse.getTruantNum()),
                    studentCourse.getProblemNum() == null ? "0" : String.valueOf(studentCourse.getProblemNum()),
                    studentCourse.getProblemAnwserNum() == null ?
                            "0" : String.valueOf(studentCourse.getProblemAnwserNum()));
            List<StatisticStudentExam> examList = examMap.get(studentCourse.getUserId());
            examList = examList == null ? Lists.newArrayList() : examList;
            for (Integer num = 0; num < examNum; num++) {
                try {
                    StatisticStudentExam exam = examList.get(num);
                    data.add(exam.getScore() == null ? "" : String.valueOf(exam.getScore()));
                } catch (Exception e) {
                    data.add("");
                }
            }
            listData.add(data);
        }
        return listData;
    }

    private String getStatusStr(Boolean submitted, Boolean checked) {
        if (submitted && checked) {
            return "已批阅";
        }
        if (submitted && !checked) {
            return "未批阅";
        }
        return "未提交";
    }

    private void convertList(List<StudentTaskDTO> tasks) {
        for (StudentTaskDTO taskDTO : tasks) {
            taskDTO.setReportStatusStr(getStatusStr(taskDTO.getReportSubmitted(), taskDTO.getReportCheck()));
            taskDTO.setReportScore(taskDTO.getReportCheck() ? taskDTO.getReportScore() : null);
        }
    }

    private void convertChapterList(List<StudentChapterDTO> list) {
        for (StudentChapterDTO chapter : list) {
            chapter.setPracticeScore(chapter.getPracticeCheck() ? chapter.getPracticeScore() : null);
            chapter.setPracticeStatusStr(getStatusStr(chapter.getPracticeSubmitted(), chapter.getPracticeCheck()));
        }
    }

    private List<StudentChapterDTO> convertErrorRate(List<ChapterStatisticDTO> chapters, Integer errorRate) {
        List<StudentChapterDTO> data = Lists.newArrayList();
        if (CollectionUtils.isEmpty(chapters)) return data;
        for (ChapterStatisticDTO chapter : chapters) {
            if (!chapter.getOpened()) continue;
            if (chapter.getPracticeErrRate() != null && chapter.getPeopleNum() > 0) {
                BigDecimal divide = new BigDecimal(chapter.getPracticeErrRate())
                        .divide(new BigDecimal(chapter.getPeopleNum()), 0, BigDecimal.ROUND_HALF_UP);
                if (divide.compareTo(new BigDecimal(errorRate)) >= 0) {
                    StudentChapterDTO chapterDTO = new StudentChapterDTO();
                    chapterDTO.setChapterId(chapter.getChapterId());
                    chapterDTO.setName(chapter.getName());
                    chapterDTO.setPracticeErrRate(divide.intValue());
                    chapterDTO.setOrderNum(chapter.getOrderNum());
                    data.add(chapterDTO);
                }
            }
        }

        // 排序
        if (CollectionUtils.isNotEmpty(data)) {
            data.sort(Comparator.comparingInt(StudentChapterDTO::getPracticeErrRate).reversed()
                    .thenComparingInt(StudentChapterDTO::getOrderNum));
        }

        return data;
    }

    private void convertStatisticStudent(List<UserInfoDTO> students, Class classInfo) {
        students.forEach(userInfo -> {
            userInfo.setStudentClassName(classInfo.getName());
            userInfo.setGrade(classInfo.getGrade());
        });
    }

    private Set<Long> queryTeacherIds(Long classId, List<Long> courseIds) {
        Set<Long> userIds = Sets.newHashSet();
        // 查询助教
        List<CourseTeacher> courseTeachers = courseService.queryCourseTeacher(courseIds, null, classId);
        if (CollectionUtils.isNotEmpty(courseTeachers)) {
            userIds.addAll(
                    courseTeachers.stream().map(CourseTeacher::getUserId).collect(Collectors.toSet())
            );
        }
        // 查询主讲教师
        if (CollectionUtils.isNotEmpty(courseIds)) {
            List<CourseBasicDTO> courses = courseService.listCourse(
                    courseIds, null, null, null, null, null);
            if (CollectionUtils.isNotEmpty(courses)) {
                userIds.addAll(
                        courses.stream().map(CourseBasicDTO::getUserId).collect(Collectors.toSet())
                );
            }
        }
        return userIds;
    }


    private List<StatisticStudent> sortStatisticStudent(List<StatisticStudent> statisticStudents, List<Long> userIds) {
        List<StatisticStudent> students = Lists.newArrayList();

        if (CollectionUtils.isEmpty(statisticStudents)) return students;

        Map<Long, StatisticStudent> map = BeanUtil.mapByKey("userId", statisticStudents);
        for (Long userId : userIds) {
            if (MapUtils.isEmpty(map) || map.get(userId) == null) {
                continue;
            }
            students.add(map.get(userId));
        }
        return students;
    }

}
